diff --git a/le_audio/certification_tool/bap_uclient_test_tool/Android.bp b/le_audio/certification_tool/bap_uclient_test_tool/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..87fb6d8daf26f23d0535841fef7beb7c5ebc1f5e --- /dev/null +++ b/le_audio/certification_tool/bap_uclient_test_tool/Android.bp @@ -0,0 +1,46 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +cc_binary { + name: "bap_uclient_test", + system_ext_specific: true, + enabled: false, + srcs: ["bap_uclient_test.cpp"], + + include_dirs: [ + ".", + "packages/modules/Bluetooth/system/include", + "packages/modules/Bluetooth/system/types", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/l2cap", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/utils/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include", + "external/libchrome", + ], + + cflags: ["-DHAS_NO_BDROID_BUILDCFG"], + + shared_libs: [ + "libcutils", + "libchrome", + "libutils", + ], + + static_libs: ["libbluetooth-types-qti"], + +} diff --git a/le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp b/le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..78db7037b02dec9f20bef606d084f19ae02d4bbb --- /dev/null +++ b/le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp @@ -0,0 +1,1904 @@ +/*********************************************************************** + * + * Copyright (c) 2014-2015, 2020 The Linux Foundation. All rights reserved. + * + * Copyright (C) 2009-2012 Broadcom Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + +******************************************************************************/ + + +/****************************************************************************** +****** + * + * Filename: bap_uclient_test.cpp + * + * Description: bap unicast client test application + * + +******************************************************************************* +****/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using bluetooth::Uuid; + +constexpr uint8_t ASE_DIRECTION_SINK = 0x01 << 0; +constexpr uint8_t ASE_DIRECTION_SRC = 0x01 << 1; + +constexpr uint8_t ASE_SINK_STEREO = 0x01 << 0; +constexpr uint8_t ASE_SRC_STEREO = 0x01 << 1; + +#ifndef BAP_UNICAST_TEST_APP_INTERFACE +#define BAP_UNICAST_TEST_APP_INTERFACE +/****************************************************************************** +****** +** Constants & Macros +******************************************************************************* +*****/ + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define PID_FILE "/data/.bdt_pid" + +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define CASE_RETURN_STR(const) case const: return #const; + + +/****************************************************************************** +****** +** Local type definitions +******************************************************************************* +*****/ + + +/****************************************************************************** +****** +** Static variables +******************************************************************************* +*****/ + +static unsigned char main_done = 0; +static int status; + +#define LE_ACL_MAX_BUFF_SIZE 4096 +static int num_frames = 1; +static unsigned long g_delay = 1; /* Default delay before data transfer */ +static int count = 1; +static uint16_t g_BleEncKeySize = 16; +static int g_le_coc_if = 0; +static int rcv_itration = 0; +static volatile bool cong_status = FALSE; + + +/* Main API */ +const bt_interface_t* sBtInterface = NULL; + +static gid_t groups[] = { AID_NET_BT, AID_INET, AID_NET_BT_ADMIN, + AID_SYSTEM, AID_MISC, AID_SDCARD_RW, + AID_NET_ADMIN, AID_VPN}; + +enum { + DISCONNECT, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +static unsigned char bt_enabled = 0; +static int g_ConnectionState = DISCONNECT; +static int g_AdapterState = BT_STATE_OFF; +static int g_PairState = BT_BOND_STATE_NONE; + +static int g_conn_id = 0; +static int g_client_if = 0; +static int g_server_if = 0; +static int g_client_if_scan = 0; +static int g_server_if_scan = 0; + + +RawAddress* remote_bd_address; + +static uint16_t g_SecLevel = 0; +static bool g_ConnType = TRUE;//DUT is initiating connection + +/****************************************************************************** +****** +** Static functions +******************************************************************************* +*****/ + +static void process_cmd(char *p, unsigned char is_job); +//static void job_handler(void *param); +static void bdt_log(const char *fmt_str, ...); +static void l2c_connect(RawAddress bd_addr); +static uint16_t do_l2cap_connect(RawAddress bd_addr); + +int GetBdAddr(char *p, RawAddress* pbd_addr); +void bdt_init(void); +int reg_inst_id = -1; +int reg_status = -1; + + +/****************************************************************************** +****** +** ASCS Client Callbacks +******************************************************************************* +*****/ + + +/****************************************************************************** +****** +** PACS client Callbacks +******************************************************************************* +*****/ + + + +/****************************************************************************** +****** +** BAP Unicast client Callbacks +******************************************************************************* +*****/ + +/****************************************************************************** +****** +** Shutdown helper functions +******************************************************************************* +*****/ + +static void bdt_shutdown(void) +{ + bdt_log("shutdown bdroid test app.\n"); + main_done = 1; +} + + +/***************************************************************************** +** Android's init.rc does not yet support applying linux capabilities +*****************************************************************************/ + +static void config_permissions(void) +{ + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap[2]; + + bdt_log("set_aid_and_cap : pid %d, uid %d gid %d", getpid(), getuid(), getgid()); + + header.pid = 0; + + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + + setuid(AID_BLUETOOTH); + setgid(AID_BLUETOOTH); + + header.version = _LINUX_CAPABILITY_VERSION_3; + + cap[CAP_TO_INDEX(CAP_NET_RAW)].permitted |= CAP_TO_MASK(CAP_NET_RAW); + cap[CAP_TO_INDEX(CAP_NET_ADMIN)].permitted |= CAP_TO_MASK(CAP_NET_ADMIN); + cap[CAP_TO_INDEX(CAP_NET_BIND_SERVICE)].permitted |= CAP_TO_MASK(CAP_NET_BIND_SERVICE); + cap[CAP_TO_INDEX(CAP_SYS_RAWIO)].permitted |= CAP_TO_MASK(CAP_SYS_RAWIO); + cap[CAP_TO_INDEX(CAP_SYS_NICE)].permitted |= CAP_TO_MASK(CAP_SYS_NICE); + cap[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); + cap[CAP_TO_INDEX(CAP_WAKE_ALARM)].permitted |= CAP_TO_MASK(CAP_WAKE_ALARM); + + cap[CAP_TO_INDEX(CAP_NET_RAW)].effective |= CAP_TO_MASK(CAP_NET_RAW); + cap[CAP_TO_INDEX(CAP_NET_ADMIN)].effective |= CAP_TO_MASK(CAP_NET_ADMIN); + cap[CAP_TO_INDEX(CAP_NET_BIND_SERVICE)].effective |= CAP_TO_MASK(CAP_NET_BIND_SERVICE); + cap[CAP_TO_INDEX(CAP_SYS_RAWIO)].effective |= CAP_TO_MASK(CAP_SYS_RAWIO); + cap[CAP_TO_INDEX(CAP_SYS_NICE)].effective |= CAP_TO_MASK(CAP_SYS_NICE); + cap[CAP_TO_INDEX(CAP_SETGID)].effective |= CAP_TO_MASK(CAP_SETGID); + cap[CAP_TO_INDEX(CAP_WAKE_ALARM)].effective |= CAP_TO_MASK(CAP_WAKE_ALARM); + + capset(&header, &cap[0]); + setgroups(sizeof(groups)/sizeof(groups[0]), groups); +} + + +/***************************************************************************** +** Logger API +*****************************************************************************/ + +void bdt_log(const char *fmt_str, ...) +{ + static char buffer[1024]; + va_list ap; + + va_start(ap, fmt_str); + vsnprintf(buffer, 1024, fmt_str, ap); + va_end(ap); + + fprintf(stdout, "%s\n", buffer); +} + +/****************************************************************************** +* + ** Misc helper functions + +*******************************************************************************/ +static const char* dump_bt_status(int status) +{ + switch(status) + { + CASE_RETURN_STR(BT_STATUS_SUCCESS) + CASE_RETURN_STR(BT_STATUS_FAIL) + CASE_RETURN_STR(BT_STATUS_NOT_READY) + CASE_RETURN_STR(BT_STATUS_NOMEM) + CASE_RETURN_STR(BT_STATUS_BUSY) + CASE_RETURN_STR(BT_STATUS_UNSUPPORTED) + + default: + return "unknown status code"; + } +} + + +/****************************************************************************** +* + ** Console helper functions + +*******************************************************************************/ + +void skip_blanks(char **p) +{ + while (**p == ' ') + (*p)++; +} + +uint32_t get_int(char **p, int DefaultValue) +{ + uint32_t Value = 0; + unsigned char UseDefault; + + UseDefault = 1; + skip_blanks(p); + + while ( ((**p)<= '9' && (**p)>= '0') ) + { + Value = Value * 10 + (**p) - '0'; + UseDefault = 0; + (*p)++; + } + if (UseDefault) + return DefaultValue; + else + return Value; +} + +int get_signed_int(char **p, int DefaultValue) +{ + int Value = 0; + unsigned char UseDefault; + unsigned char NegativeNum = 0; + + UseDefault = 1; + skip_blanks(p); + + if ((**p) == '-') + { + NegativeNum = 1; + (*p)++; + } + while ( ((**p)<= '9' && (**p)>= '0') ) + { + Value = Value * 10 + (**p) - '0'; + UseDefault = 0; + (*p)++; + } + + if (UseDefault) + return DefaultValue; + else + return ((NegativeNum == 0)? Value : -Value); +} + +void get_str_1(char **p, char *Buffer) +{ + skip_blanks(p); + while (**p != 0 && **p != '\0') + { + *Buffer = **p; + (*p)++; + Buffer++; + } + + *Buffer = 0; +} + +void get_str(char **p, char *Buffer) +{ + skip_blanks(p); + while (**p != 0 && **p != ' ') + { + *Buffer = **p; + (*p)++; + Buffer++; + } + + *Buffer = 0; +} + + + +#define is_cmd(str) ((strlen(str) == strlen(cmd)) && strncmp((const char *)&cmd, str, strlen(str)) == 0) +#define if_cmd(str) if (is_cmd(str)) + +typedef void (t_console_cmd_handler) (char *p); + +typedef struct { + const char *name; + t_console_cmd_handler *handler; + const char *help; + unsigned char is_job; +} t_cmd; + +void do_help(char *p); +void do_quit(char *p); +void do_init(char *p); +void do_enable(char *p); +void do_disable(char *p); +void do_cleanup(char *p); +void do_pairing(char *p); +void do_pacs_discovery(char *p); +void do_ascs_discovery(char *p); +void do_bap_connect(char *p); +void do_bap_disconnect(char *p); +void do_bap_start(char *p); +void do_bap_stop(char *p); +void do_bap_disc_in_connecting(char *p); +void do_bap_disc_in_starting(char *p); +void do_bap_disc_in_stopping(char *p); +void do_bap_stop_in_starting(char *p); +void do_bap_update_stream(char *p); + +/******************************************************************* + * + * CONSOLE COMMAND TABLE + * +*/ + +const t_cmd console_cmd_list[] = +{ + /* + * INTERNAL + */ + + { "help", do_help, "lists all available console commands", 0 }, + { "quit", do_quit, "", 0}, + + /* + * API CONSOLE COMMANDS + */ + + /* Init and Cleanup shall be called automatically */ + { "enable", do_enable, "cmd :: enable", 0 }, + { "disable", do_disable, "cmd :: disable", 0 }, + { "pair", do_pairing, "cmd :: pair ", 0 }, + { "pacs_discovery", do_pacs_discovery, "cmd :: pacs_discovery ", 0 }, + { "ascs_discovery", do_ascs_discovery, "cmd :: ascs_discovery ", 0 }, + { "bap_connect", do_bap_connect, "cmd :: bap_connect ", 0 }, + { "bap_disconnect", do_bap_disconnect, "cmd :: bap_disconnect ", 0 }, + { "bap_start", do_bap_start, "cmd :: bap_start ", 0 }, + { "bap_stop", do_bap_stop, "cmd :: bap_stop ", 0 }, + { "bap_disc_in_connecting", do_bap_disc_in_connecting, "cmd :: bap_disc_in_connecting ", 0 }, + { "bap_disc_in_starting", do_bap_disc_in_starting, "cmd :: bap_disc_in_starting ", 0 }, + { "bap_disc_in_stopping", do_bap_disc_in_stopping, "cmd :: bap_disc_in_stopping ", 0 }, + { "bap_stop_in_starting", do_bap_stop_in_starting, "cmd :: bap_stop_in_starting ", 0 }, + { "bap_update_stream", do_bap_update_stream, "cmd :: bap_update_stream ", 0 }, + /* last entry */ + {NULL, NULL, "", 0}, +}; + + +static int console_cmd_maxlen = 0; + +static void *cmdjob_handler(void *param) +{ + char *job_cmd = (char*)param; + + bdt_log("cmdjob starting (%s)", job_cmd); + + process_cmd(job_cmd, 1); + + bdt_log("cmdjob terminating"); + + free(job_cmd); + return NULL; +} + +static int create_cmdjob(char *cmd) +{ + pthread_t thread_id; + char *job_cmd; + + job_cmd = (char*)calloc(1, strlen(cmd)+1); /* freed in job handler */ + if (job_cmd) { + strlcpy(job_cmd, cmd,(strlen(cmd)+1)); + if (pthread_create(&thread_id, NULL, cmdjob_handler, (void *)job_cmd) != 0) + /*if (pthread_create(&thread_id, NULL, + (void*)cmdjob_handler, (void*)job_cmd) !=0)*/ + perror("pthread_create"); + return 0; + } + else + perror("create_Cmdjob malloc failed "); + return -1; +} + +/****************************************************************************** +* + ** Load stack lib + +*******************************************************************************/ +#define BLUETOOTH_LIBRARY_NAME "libbluetooth_qti.so" +int load_bt_lib(const bt_interface_t** interface) { + const char* sym = BLUETOOTH_INTERFACE_STRING; + bt_interface_t* itf = nullptr; + + // Always try to load the default Bluetooth stack on GN builds. + const char* path = BLUETOOTH_LIBRARY_NAME; + void* handle = dlopen(path, RTLD_NOW); + if (!handle) { + //const char* err_str = dlerror(); + printf("failed to load Bluetooth library\n"); + goto error; + } + + // Get the address of the bt_interface_t. + itf = (bt_interface_t*)dlsym(handle, sym); + if (!itf) { + printf("failed to load symbol from Bluetooth library\n"); + goto error; + } + + // Success. + printf(" loaded HAL Success\n"); + *interface = itf; + return 0; + +error: + *interface = NULL; + if (handle) dlclose(handle); + + return -EINVAL; +} + +int HAL_load(void) +{ + if (load_bt_lib((bt_interface_t const**)&sBtInterface)) { + printf("No Bluetooth Library found\n"); + return -1; + } + return 0; +} + +int HAL_unload(void) +{ + int err = 0; + + bdt_log("Unloading HAL lib"); + + sBtInterface = NULL; + + bdt_log("HAL library unloaded (%s)", strerror(err)); + + return err; +} + +/****************************************************************************** +* + ** HAL test functions & callbacks + +*******************************************************************************/ + +void setup_test_env(void) +{ + int i = 0; + + while (console_cmd_list[i].name != NULL) + { + console_cmd_maxlen = MAX(console_cmd_maxlen, (int)strlen(console_cmd_list[i].name)); + i++; + } +} + +void check_return_status(int status) +{ + if (status != BT_STATUS_SUCCESS) + { + bdt_log("HAL REQUEST FAILED status : %d (%s)", status, dump_bt_status(status)); + } + else + { + bdt_log("HAL REQUEST SUCCESS"); + } +} + +static void do_set_localname(char *p) +{ + printf("set name in progress: %s\n", p); + bt_property_t property = {BT_PROPERTY_BDNAME, static_cast(strlen(p)), p}; + status = sBtInterface->set_adapter_property(&property); +} + +static void adapter_state_changed(bt_state_t state) +{ + int V1 = 1000, V2=2; + char V3[] = "bap_uclient_test"; + bt_property_t property = {(bt_property_type_t)9 /* +BT_PROPERTY_DISCOVERY_TIMEOUT*/, 4, &V1}; + bt_property_t property1 = {(bt_property_type_t)7 /*SCAN*/, 2, &V2}; + bt_property_t property2 ={(bt_property_type_t)1,9, &V3}; + printf("ADAPTER STATE UPDATED : %s\n", (state == BT_STATE_OFF)?"OFF":"ON"); + + g_AdapterState = state; + + if (state == BT_STATE_ON) { + bt_enabled = 1; + status = sBtInterface->set_adapter_property(&property1); + status = sBtInterface->set_adapter_property(&property); + status = sBtInterface->set_adapter_property(&property2); + } else { + bt_enabled = 0; + } +} + +static void adapter_properties_changed(bt_status_t status, + int num_properties, bt_property_t *properties) +{ + char Bd_addr[15] = {0}; + if(NULL == properties) + { + printf("properties is null\n"); + return; + } + switch(properties->type) + { + case BT_PROPERTY_BDADDR: + memcpy(Bd_addr, properties->val, properties->len); + break; + default: + printf("property type not used\n"); + } + return; +} + +static void discovery_state_changed(bt_discovery_state_t state) +{ + printf("Discovery State Updated : %s\n", + (state == BT_DISCOVERY_STOPPED)?"STOPPED":"STARTED"); +} + + +static void pin_request_cb(RawAddress* remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod, bool min_16_digit ) +{ + remote_bd_address = remote_bd_addr; + //bt_pin_code_t pincode = {{0x31, 0x32, 0x33, 0x34}}; + printf("Enter the pin key displayed in the remote device and terminate the key entry with .\n"); + + /*if(BT_STATUS_SUCCESS != sBtInterface->pin_reply(remote_bd_addr, TRUE, 4 +, &pincode)) + { + printf("Pin Reply failed\n"); + }*/ +} +static void ssp_request_cb(RawAddress* remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod, bt_ssp_variant_t pairing_variant, +uint32_t pass_key) +{ + printf("ssp_request_cb : name=%s variant=%d passkey=%u\n", bd_name->name, +pairing_variant, pass_key); + if(BT_STATUS_SUCCESS != sBtInterface->ssp_reply(remote_bd_addr, +pairing_variant, TRUE, pass_key)) + { + printf("SSP Reply failed\n"); + } +} + +static void bond_state_changed_cb(bt_status_t status, RawAddress* +remote_bd_addr, bt_bond_state_t state) +{ + g_PairState = state; +} + +static void acl_state_changed(bt_status_t status, RawAddress* remote_bd_addr, +bt_acl_state_t state, + bt_hci_error_code_t hci_reason) +{ + printf("acl_state_changed : remote_bd_addr=%02x:%02x:%02x:%02x:%02x:%02x, \ + acl status=%s \n", + remote_bd_addr->address[0], remote_bd_addr->address[1], remote_bd_addr->address[2], + remote_bd_addr->address[3], remote_bd_addr->address[4], remote_bd_addr->address[5], + (state == BT_ACL_STATE_CONNECTED)?"ACL Connected" :"ACL Disconnected"); +} +static void dut_mode_recv(uint16_t opcode, uint8_t *buf, uint8_t len) +{ + bdt_log("DUT MODE RECV : NOT IMPLEMENTED"); +} + +static void le_test_mode(bt_status_t status, uint16_t packet_count) +{ + bdt_log("LE TEST MODE END status:%s number_of_packets:%d", + dump_bt_status(status), packet_count); +} + +extern int timer_create (clockid_t, struct sigevent *__restrict, timer_t * +__restrict); +extern int timer_settime (timer_t, int, const struct itimerspec *__restrict, +struct itimerspec *__restrict); + +static bool set_wake_alarm(uint64_t delay_millis, bool should_wake, alarm_cb +cb, void *data) +{ + + static timer_t timer; + static bool timer_created; + + if (!timer_created) { + struct sigevent sigevent; + memset(&sigevent, 0, sizeof(sigevent)); + sigevent.sigev_notify = SIGEV_THREAD; + sigevent.sigev_notify_function = (void (*)(union sigval))cb; + sigevent.sigev_value.sival_ptr = data; + timer_create(CLOCK_MONOTONIC, &sigevent, &timer); + timer_created = true; + } + + struct itimerspec new_value; + new_value.it_value.tv_sec = delay_millis / 1000; + new_value.it_value.tv_nsec = (delay_millis % 1000) * 1000 * 1000; + new_value.it_interval.tv_sec = 0; + new_value.it_interval.tv_nsec = 0; + timer_settime(timer, 0, &new_value, NULL); + + return TRUE; +} + +static int acquire_wake_lock(const char *lock_name) +{ + return BT_STATUS_SUCCESS; +} + +static int release_wake_lock(const char *lock_name) +{ + return BT_STATUS_SUCCESS; +} + +static bt_callbacks_t bt_callbacks = { + sizeof(bt_callbacks_t), + adapter_state_changed, + adapter_properties_changed, /*adapter_properties_cb */ + NULL, /* remote_device_properties_cb */ + NULL, /* device_found_cb */ + discovery_state_changed, /* discovery_state_changed_cb */ + pin_request_cb, /* pin_request_cb */ + ssp_request_cb, /* ssp_request_cb */ + bond_state_changed_cb, /*bond_state_changed_cb */ + acl_state_changed, /* acl_state_changed_cb */ + NULL, /* thread_evt_cb */ + dut_mode_recv, /*dut_mode_recv_cb */ + le_test_mode, /* le_test_mode_cb */ + NULL /*energy_info_cb*/ +}; + +static bt_os_callouts_t bt_os_callbacks = { + sizeof(bt_os_callouts_t), + set_wake_alarm, + acquire_wake_lock, + release_wake_lock +}; + + +void bdt_enable(void) +{ + bdt_log("ENABLE BT"); + if (bt_enabled) { + bdt_log("Bluetooth is already enabled"); + return; + } + status = sBtInterface->enable(); + + check_return_status(status); +} + +void bdt_disable(void) +{ + bdt_log("DISABLE BT"); + if (!bt_enabled) { + bdt_log("Bluetooth is already disabled"); + return; + } + status = sBtInterface->disable(); + + check_return_status(status); +} + +void do_pairing(char *p) +{ + RawAddress bd_addr = {{0}}; + int transport = GATT_TRANSPORT_LE; + if(FALSE == GetBdAddr(p, &bd_addr)) return; // arg1 + if(BT_STATUS_SUCCESS != sBtInterface->create_bond(&bd_addr, transport)) + { + printf("Failed to Initiate Pairing \n"); + return; + } +} + + +void bdt_cleanup(void) +{ + bdt_log("CLEANUP"); + sBtInterface->cleanup(); +} + +/****************************************************************************** +* + ** Console commands + +*******************************************************************************/ + +void do_help(char *p) +{ + int i = 0; + char line[128]; +// int pos = 0; + + while (console_cmd_list[i].name != NULL) + { + snprintf(line, 128,"%s", (char*)console_cmd_list[i].name); + bdt_log("%s %s\n", (char*)line, (char*)console_cmd_list[i].help); + i++; + } +} + +void do_quit(char *p) +{ + bdt_shutdown(); +} + +/******************************************************************* + * + * BT TEST CONSOLE COMMANDS + * + * Parses argument lists and passes to API test function + * +*/ + +void do_init(char *p) +{ + bdt_init(); +} + +void do_enable(char *p) +{ + bdt_enable(); +} + +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::pacs::PacsClientCallbacks; + +static PacsClientInterface* sPacsClientInterface = nullptr; +static uint16_t pacs_client_id = 0; +static uint8_t pacsSearchComplete = 0; +static uint8_t pacsConnectionComplete = 0; +static uint8_t bapConnectionComplete = 0; +static RawAddress pac_bd_addr; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, + int client_id) override { + printf("%d\n", client_id); + pacs_client_id = client_id; + } + void OnConnectionState(const RawAddress& bd_addr, + bluetooth::bap::pacs::ConnectionState state) +override { + printf("%s\n", __func__); + if(state == bluetooth::bap::pacs::ConnectionState::CONNECTED) { + printf("%s Connected\n", __func__); + pacsConnectionComplete = 1; + } else if(state == bluetooth::bap::pacs::ConnectionState::DISCONNECTED) { + printf("%s Disconnected\n", __func__); + } + } + void OnAudioContextAvailable(const RawAddress& bd_addr, + uint32_t available_contexts) override { + printf("%s\n", __func__); + } + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + pacsSearchComplete = 1; + printf("%s\n", __func__); + } +}; + +static PacsClientCallbacksImpl sPacsClientCallbacks; + +void do_pacs_discovery(char *p) +{ + if(FALSE == GetBdAddr(p, &pac_bd_addr)) return; // arg1 + sPacsClientInterface = (PacsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_PACS_CLIENT_ID); + sPacsClientInterface->Init(&sPacsClientCallbacks); + sleep(1); + printf("%s going for connect\n", __func__); + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + while(!pacsConnectionComplete) sleep(1); + printf("%s going for discovery\n", __func__); + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + while(!pacsSearchComplete) sleep(1); + printf("%s going for disconnect\n", __func__); + sleep(5); + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); +} + +using bluetooth::bap::ascs::AscsClientInterface; +using bluetooth::bap::ascs::AscsClientCallbacks; + +static AscsClientInterface* sAscsClientInterface = nullptr; +static uint16_t ascs_client_id = 0; +static uint8_t ascsSearchComplete = 0; +static uint8_t ascsConnectionComplete = 0; +static RawAddress ascs_bd_addr; + +class AscsClientCallbacksImpl : public AscsClientCallbacks { + public: + ~AscsClientCallbacksImpl() = default; + void OnAscsInitialized(int status, int client_id) override { + printf("%d\n", client_id); + ascs_client_id = client_id; + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::ascs::GattState state) override { + printf("%s\n", __func__); + if(state == bluetooth::bap::ascs::GattState::CONNECTED) { + printf("%s Connected\n", __func__); + ascsConnectionComplete = 1; + } else if(state == bluetooth::bap::ascs::GattState::DISCONNECTED) { + printf("%s Disconnected\n", __func__); + } + } + + void OnAseOpFailed(const RawAddress& address, + bluetooth::bap::ascs::AseOpId ase_op_id, + std::vector status) { + printf("%s\n", __func__); + + } + + void OnAseState(const RawAddress& address, + bluetooth::bap::ascs::AseParams ase) override { + printf("%s\n", __func__); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + printf("%s\n", __func__); + ascsSearchComplete = 1; + } +}; + +static AscsClientCallbacksImpl sAscsClientCallbacks; + +void do_ascs_discovery(char *p) +{ + if(FALSE == GetBdAddr(p, &ascs_bd_addr)) return; // arg1 + sAscsClientInterface = (AscsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_ASCS_CLIENT_ID); + sAscsClientInterface->Init(&sAscsClientCallbacks); + sleep(1); + printf("%s going for connect\n", __func__); + sAscsClientInterface->Connect(ascs_client_id, ascs_bd_addr); + while(!ascsConnectionComplete) sleep(1); + printf("%s going for discovery\n", __func__); + sAscsClientInterface->StartDiscovery(ascs_client_id, ascs_bd_addr); + while(!ascsSearchComplete) sleep(1); + printf("%s going for disconnect\n", __func__); + sAscsClientInterface->Disconnect(ascs_client_id, ascs_bd_addr); +} + +template +std::string loghex(T x) { + std::stringstream tmp; + tmp << "0x" << std::internal << std::hex << std::setfill('0') + << std::setw(sizeof(T) * 2) << (unsigned int)x; + return tmp.str(); +} + +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClientInterface; + +static UcastClientInterface* sUcastClientInterface = nullptr; + +class UcastClientCallbacksImpl : public UcastClientCallbacks { + public: + ~UcastClientCallbacksImpl() = default; + void OnStreamState(const RawAddress &address, + std::vector streams_state_info) override { + for (auto it = streams_state_info.begin(); + it != streams_state_info.end(); it++) { + printf("%s stream type %d\n", __func__, (it->stream_type.type)); + printf("%s stream dir %s\n", __func__, loghex(it->stream_type.direction).c_str()); + printf("%s stream state %d\n", __func__, static_cast (it->stream_state)); + if(static_cast (it->stream_state) == 2 || + static_cast (it->stream_state) == 0) { + bapConnectionComplete = 1; + } + } + } + void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) override { + printf("%s\n",__func__); + } + void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + printf("%s\n",__func__); + } +}; + +static UcastClientCallbacksImpl sUcastClientCallbacks; + +typedef struct { + char bdAddr[13]; + uint16_t profile; + uint16_t context; + uint8_t direction; +} Servers; + +typedef struct { + uint8_t cnt; + char codecConfig[7]; + char audioConfig[5]; + std::vector serv; +} UserParms; + +typedef struct { + uint8_t audio_dir; + uint8_t stereo; +} AudioType; + +typedef struct { + uint8_t num_servers; + uint8_t num_cises; + std::vector audio_type; +} AudioConfigSettings; + +// + +std::map audioConfigMap = { + {"1_1", {1, 1, {{ASE_DIRECTION_SINK, 0}}}}, // EB streaming + {"2_1", {1, 1, {{ASE_DIRECTION_SRC, 0}}}}, // EB Recording + {"3_1", {1, 1, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}, // EB Call Mono Bi-Dir CIS + {"4_1", {1, 1, {{ASE_DIRECTION_SINK, ASE_SINK_STEREO}}}}, // Stereo Headset stereo streaming + + {"5_1", {1, 1, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, ASE_SINK_STEREO}}}}, // EB Call with speaker stereo mono mic + + {"6_1", {1, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SINK, 0}}}}, // TWM Streaming + {"6_2", {2, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SINK, 0}}}}, // EBP Streaming same as 1_1 + {"7_1", {1, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC, 0}}}}, // EB Call with dual CIS ( same as 3_1) + {"7_2", {2, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC, 0}}}}, // EBP Call with speaker on EB1 and mic on EB2 + {"8_1", {1, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}, // Headset Call with single mic + {"8_2", {2, 2, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SINK, 0}}}}, // EBP Call with mic from one EB + {"9_1", {1, 2, {{ASE_DIRECTION_SRC, 0}, {ASE_DIRECTION_SRC, 0}}}}, // TWM Recording + {"9_2", {2, 2, {{ASE_DIRECTION_SRC, 0}, {ASE_DIRECTION_SRC, 0}}}}, // EBP Recording +{"10_1", {1, 1, {{ASE_DIRECTION_SRC, ASE_SRC_STEREO}}}}, // EB stereo Recording +{"11_1", {1, 2, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}, // TWM Call +{"11_2", {2, 2, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}}; // EBP Call + +int getInt(std::string &str) +{ + int ret; + std::stringstream integer(str); + integer >> ret; + return ret; +} + +void parse_parms(char *p, UserParms *ptr) +{ + std::string line(p); + std::vector token; + std::stringstream check1(line); + std::string intermediate; + while(getline(check1, intermediate, ' ')) + { + token.push_back(intermediate); + } + ptr->cnt = token.size(); + if (ptr->cnt == 11) + { + memcpy(ptr->codecConfig, token[1].c_str(), token[1].size()); + memcpy(ptr->audioConfig, token[2].c_str(), token[2].size()); + Servers serv1, serv2; + memcpy(serv1.bdAddr, token[3].c_str(), token[3].size()); + serv1.profile = static_cast(getInt(token[4])); + serv1.direction = static_cast(getInt(token[5])); + serv1.context = static_cast(getInt(token[6])); + ptr->serv.push_back(serv1); + memcpy(serv2.bdAddr, token[7].c_str(), token[7].size()); + serv2.profile = static_cast(getInt(token[8])); + serv2.direction = static_cast(getInt(token[9])); + serv2.context = static_cast(getInt(token[10])); + ptr->serv.push_back(serv2); + } + else if (ptr->cnt == 9) + { + Servers serv1, serv2; + memcpy(serv1.bdAddr, token[1].c_str(), token[1].size()); + serv1.profile = static_cast(getInt(token[2])); + serv1.direction = static_cast(getInt(token[3])); + serv1.context = static_cast(getInt(token[4])); + ptr->serv.push_back(serv1); + memcpy(serv2.bdAddr, token[5].c_str(), token[5].size()); + serv2.profile = static_cast(getInt(token[6])); + serv2.direction = static_cast(getInt(token[7])); + serv2.context = static_cast(getInt(token[8])); + ptr->serv.push_back(serv2); + } + else if (ptr->cnt == 7) + { + memcpy(ptr->codecConfig, token[1].c_str(), token[1].size()); + memcpy(ptr->audioConfig, token[2].c_str(), token[2].size()); + Servers serv1; + memcpy(serv1.bdAddr, token[3].c_str(), token[3].size()); + serv1.profile = static_cast(getInt(token[4])); + serv1.direction = static_cast(getInt(token[5])); + serv1.context = static_cast(getInt(token[6])); + ptr->serv.push_back(serv1); + } + else if (ptr->cnt == 5) + { + Servers serv1; + memcpy(serv1.bdAddr, token[1].c_str(), token[1].size()); + serv1.profile = static_cast(getInt(token[2])); + serv1.direction = static_cast(getInt(token[3])); + serv1.context = static_cast(getInt(token[4])); + ptr->serv.push_back(serv1); + } + else + { + printf("%s ERROR: Input\n", __func__); + } +} + +constexpr uint8_t CONFIG_FRAME_DUR_INDEX = 0x04; +constexpr uint8_t CONFIG_OCTS_PER_FRAME_INDEX = 0x04; +constexpr uint8_t CONFIG_PREF_AUDIO_CONT_INDEX = 0x06; // CS1 + +bool UpdateFrameDuration(bluetooth::bap::pacs::CodecConfig *config , + uint8_t frame_dur) { + uint64_t value = 0xFF; + config->codec_specific_1 &= + ~(value << (CONFIG_FRAME_DUR_INDEX*8)); + config->codec_specific_1 |= + static_cast(frame_dur) << (CONFIG_FRAME_DUR_INDEX * 8); + return true; +} + + +bool UpdatePreferredAudioContext(bluetooth::bap::pacs::CodecConfig *config , + uint16_t pref_audio_context) { + uint64_t value = 0xFFFF; + config->codec_specific_1 &= ~(value << (CONFIG_PREF_AUDIO_CONT_INDEX*8)); + config->codec_specific_1 |= static_cast(pref_audio_context) << + (CONFIG_PREF_AUDIO_CONT_INDEX * 8); + return true; +} + +bool UpdateOctsPerFrame(bluetooth::bap::pacs::CodecConfig *config , + uint16_t octs_per_frame) { + uint64_t value = 0xFFFF; + config->codec_specific_2 &= + ~(value << (CONFIG_OCTS_PER_FRAME_INDEX * 8)); + config->codec_specific_2 |= + static_cast(octs_per_frame) << (CONFIG_OCTS_PER_FRAME_INDEX * 8); + return true; +} + +void set_conn_info(bluetooth::bap::ucast::StreamConnect *conn_info, int type, int context, int dir) +{ + conn_info->stream_type.type = type; + conn_info->stream_type.direction = dir; + conn_info->stream_type.audio_context = context; +} + +bluetooth::bap::pacs::CodecSampleRate get_sample_rate (char *p) +{ + std::string str = p; + if (str.find("16_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_16000; + else if (str.find("24_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_24000; + else if (str.find("32_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_32000; + else if (str.find("48_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_48000; + else if (str.find("8_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_8000; + else + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_NONE; +} + +int get_frame_duration (char *p) +{ + std::string str = p; + int ret; + if ((str.find("_1_") != std::string::npos) || + (str.find("_3_") != std::string::npos) || + (str.find("_5_") != std::string::npos)) + ret = static_cast(bluetooth::bap::pacs::CodecFrameDuration::FRAME_DUR_7_5); + else if ((str.find("_2_") != std::string::npos) || + (str.find("_4_") != std::string::npos) || + (str.find("_6_") != std::string::npos)) + ret = static_cast(bluetooth::bap::pacs::CodecFrameDuration::FRAME_DUR_10); + else + ret = -1; + return ret; +} + +int get_sdu_interval (char *p) +{ + std::string str = p; + int ret; + if ((str.find("_1_") != std::string::npos) || + (str.find("_3_") != std::string::npos) || + (str.find("_5_") != std::string::npos)) + ret = 7500; + else if ((str.find("_2_") != std::string::npos) || + (str.find("_4_") != std::string::npos) || + (str.find("_6_") != std::string::npos)) + ret = 10000; + else + ret = -1; + return ret; +} + +std::map octetPerFrame = +{{"8_1", 26},{"8_2", 30},{"16_1", 30},{"16_2", 40}, +{"24_1", 45},{"24_2", 60},{"32_1", 60},{"32_2", 80}, +{"48_1", 75},{"48_2", 100},{"48_3", 90},{"48_4", 120}, +{"48_5", 117},{"48_6", 155}}; + +int get_octetPerFrame (char *p) +{ + std::string str = p; + int ret = -1; + size_t pos = str.rfind('_'); + std::string key = str.substr(0, pos); + for (std::map::iterator it = + octetPerFrame.begin(); it != octetPerFrame.end(); it++) + { + if (key.compare(it->first) == 0) + ret = it->second; + } + return ret; +} + +std::map tport_latency = +{{"8_1_1", 8},{"16_1_1", 8},{"24_1_1", 8},{"32_1_1", 8}, + {"8_2_1", 10},{"16_2_1", 10},{"24_2_1", 10},{"32_2_1", 10}, +{"48_1_1", 15},{"48_3_1", 15},{"48_5_1", 15}, +{"48_2_1", 20},{"48_4_1", 20},{"48_6_1", 20}, + {"8_1_2", 75},{"16_1_2", 75},{"24_1_2", 75}, +{"31_1_2", 75},{"48_1_2", 75},{"48_3_2", 75},{"48_5_2", 75}, + {"8_2_2", 95},{"16_2_2", 95},{"24_2_2", 95}, +{"32_2_2", 95},{"48_2_2", 95}, +{"48_4_2", 100},{"48_6_2", 100}}; + +int get_tport_latency (char *p) +{ + std::string str = p; + for (std::map::iterator it = tport_latency.begin(); + it != tport_latency.end(); it++) + { + if (str.compare(it->first) == 0) + return it->second; + } + return -1; +} + +int get_rtn (char *p) +{ + std::string str = p; + int ret; + size_t pos = str.rfind('_'); + std::string key = str.substr(pos); + if (str.find("_1_2") != std::string::npos || + str.find("_2_2") != std::string::npos || + str.find("_3_2") != std::string::npos || + str.find("_4_2") != std::string::npos || + str.find("_5_2") != std::string::npos || + str.find("_6_2") != std::string::npos ) { + ret = 13; + return ret; + } + if (str.find("48_") != std::string::npos) + ret = 5; + if ((str.find("8_") != std::string::npos) || + (str.find("16_") != std::string::npos) || + (str.find("24_") != std::string::npos) || + (str.find("32_") != std::string::npos)) + ret = 2; + else + ret = -1; + return ret; +} + +int getAudioConfigSettings(char *p, AudioConfigSettings *ptr) +{ + int ret = -1; + std::string key = p; + for (std::map::iterator it = + audioConfigMap.begin(); + it != audioConfigMap.end(); it++) + { + if (key.compare(it->first) == 0) + { + *ptr = it->second; + printf(" %s ERROR: audio type 0 %d \n", __func__, ptr->audio_type[0].audio_dir); + printf(" %s ERROR: audio type 1 %d \n", __func__, ptr->audio_type[1].audio_dir); + //memcpy(ptr, &it->second, sizeof(AudioConfigSettings)); + ret = 0; + } + } + return ret; +} + +typedef struct +{ + uint8_t A; + uint8_t B; + uint8_t C; +} setFormat; + +void set(void *dest, setFormat src) +{ + memcpy(dest, &src, sizeof(setFormat)); +} + +void set_codec_qos_config (bluetooth::bap::ucast::CodecQosConfig *codec_qos_config, + char *codecConfig, AudioConfigSettings *acs, + uint8_t audio_direction, uint16_t context, + uint8_t server_id, + uint8_t server_count, uint8_t total_servers) +{ + int frameDuration, octetPerFrame, tport_latency, sdu_interval, rtn, cis_t; + bluetooth::bap::pacs::CodecSampleRate sampleRate; + bool stereo_t = false; + bluetooth::bap::ucast::CIGConfig cig_config; + sampleRate = get_sample_rate(codecConfig); + printf("Sample Rate %d\n", sampleRate); + printf("server_id %d\n", server_id); + + if (sampleRate == bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_NONE) + { + printf(" %s ERROR: sample rate\n", __func__); + exit(0); + } + frameDuration = get_frame_duration(codecConfig); + if (frameDuration < 0) + { + printf(" %s ERROR: frame duration\n", __func__); + exit(0); + } + octetPerFrame = get_octetPerFrame(codecConfig); + if (octetPerFrame < 0) + { + printf(" %s ERROR: octet per frame\n", __func__); + exit(0); + } + tport_latency = get_tport_latency(codecConfig); + if (tport_latency < 0) + { + printf(" %s ERROR: max transport latency\n", __func__); + exit(0); + } + rtn = get_rtn(codecConfig); + if (rtn < 0) + { + printf(" %s ERROR: re-transmission\n", __func__); + exit(0); + } + sdu_interval = get_sdu_interval(codecConfig); + codec_qos_config->codec_config.codec_type = + bluetooth::bap::pacs::CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config->codec_config.codec_priority = + bluetooth::bap::pacs::CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config->codec_config.sample_rate = sampleRate; + UpdateFrameDuration(&codec_qos_config->codec_config, + static_cast(frameDuration)); + UpdatePreferredAudioContext(&codec_qos_config->codec_config, context); + cig_config.cig_id = 1; + cig_config.cis_count = acs->num_cises; + cig_config.packing = 0x01; // interleaved + cig_config.framing = 0x00; // unframed + cig_config.max_tport_latency_m_to_s = static_cast(tport_latency); + cig_config.max_tport_latency_s_to_m = static_cast(tport_latency); + if (sdu_interval == 7500) + { + set(&cig_config.sdu_interval_m_to_s, {0x4C, 0x1D, 0x00}); + set(&cig_config.sdu_interval_s_to_m, {0x4C, 0x1D, 0x00}); + } + else + { + set(&cig_config.sdu_interval_m_to_s, {0x10, 0x27, 0x00}); + set(&cig_config.sdu_interval_s_to_m, {0x10, 0x27, 0x00}); + } + memcpy(&codec_qos_config->qos_config.cig_config, + &cig_config, sizeof(cig_config)); + for (uint8_t i = 0; i < acs->num_cises; i++) { + bluetooth::bap::ucast::CISConfig cis_config; + bluetooth::bap::ucast::ASCSConfig ascs_config; + int max_sdu_m_to_s; + int max_sdu_s_to_m; + cis_config.cis_id = i; + ascs_config.cig_id = 1; + ascs_config.cis_id = i; + max_sdu_m_to_s = max_sdu_s_to_m = get_octetPerFrame(codecConfig); + printf("audio_dir %d\n", acs->audio_type[i].audio_dir); + printf("max_sdu_m_to_s %d\n", max_sdu_m_to_s); + printf("max_sdu_s_to_m %d\n", max_sdu_s_to_m); + if(acs->audio_type[i].stereo & ASE_SRC_STEREO) { + max_sdu_s_to_m *= 2; + if(audio_direction & ASE_DIRECTION_SRC) stereo_t = true; + } + if(acs->audio_type[i].stereo & ASE_SINK_STEREO) { + max_sdu_m_to_s *= 2; + if(audio_direction & ASE_DIRECTION_SINK) stereo_t = true; + } + + if (acs->audio_type[i].audio_dir == (ASE_DIRECTION_SINK|ASE_DIRECTION_SRC)) + { + printf("i %d Filling both m to s and s to m \n", i); + cis_config.max_sdu_m_to_s = static_cast(max_sdu_m_to_s); + cis_config.max_sdu_s_to_m = static_cast(max_sdu_s_to_m); + ascs_config.bi_directional = true; + } + else if (acs->audio_type[i].audio_dir == ASE_DIRECTION_SRC) + { + printf("i %d Filling s to m \n", i); + cis_config.max_sdu_s_to_m = static_cast(max_sdu_s_to_m); + cis_config.max_sdu_m_to_s = 0; + ascs_config.bi_directional = false; + } + else if (acs->audio_type[i].audio_dir == ASE_DIRECTION_SINK) + { + printf("i %d Filling m to s \n", i); + cis_config.max_sdu_m_to_s = static_cast(max_sdu_m_to_s); + cis_config.max_sdu_s_to_m = 0; + ascs_config.bi_directional = false; + } + cis_config.phy_m_to_s = 0x02; + cis_config.phy_s_to_m = 0x02; + cis_config.rtn_m_to_s = static_cast(rtn); + cis_config.rtn_s_to_m = static_cast(rtn); + printf("rtn %d \n", rtn); + set(&ascs_config.presentation_delay, {0x40, 0x9C, 0x00}); + codec_qos_config->qos_config.cis_configs.push_back(cis_config); + + + printf("i %d server_id %d \n", i, server_id); + if(total_servers == 1) { + if(acs->num_cises == 1) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } else if(acs->num_cises == 2) { + if(acs->audio_type[i % acs->num_cises].audio_dir == + acs->audio_type[(i + 1) % acs->num_cises].audio_dir) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } else if( i == server_count) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } + } + } else if(total_servers == 2) { + if(i == server_id) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } + } + } + if (stereo_t == true) { + codec_qos_config->codec_config.channel_mode = + bluetooth::bap::pacs::CodecChannelMode::CODEC_CHANNEL_MODE_STEREO; + UpdateOctsPerFrame(&codec_qos_config->codec_config, + static_cast(octetPerFrame*2)); + } else { + codec_qos_config->codec_config.channel_mode = + bluetooth::bap::pacs::CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + UpdateOctsPerFrame(&codec_qos_config->codec_config, + static_cast(octetPerFrame)); + } +} + +void do_bap_connect (char *p) +{ + UserParms args; + parse_parms(p, &args); + bluetooth::bap::ucast::CodecQosConfig codec_qos_config; + bluetooth::bap::ucast::CodecQosConfig codec_qos_config_2; + AudioConfigSettings acs; + bapConnectionComplete = 0; + if (getAudioConfigSettings(args.audioConfig, &acs) < 0) + { + printf("%s ERROR: AudioConfig\n", __func__); + exit(0); + } + //set_codec_qos_config(&codec_qos_config, args.codecConfig, &acs); + for (uint8_t i = 0; i < args.serv.size(); i++) { + RawAddress bap_bd_addr; + bluetooth::bap::ucast::StreamConnect conn_info; + std::vector streams; + codec_qos_config.qos_config.cis_configs.clear(); + codec_qos_config.qos_config.ascs_configs.clear(); + codec_qos_config_2.qos_config.cis_configs.clear(); + codec_qos_config_2.qos_config.ascs_configs.clear(); + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + if (args.serv[i].direction & ASE_DIRECTION_SINK) + { + set_codec_qos_config(&codec_qos_config, args.codecConfig, + &acs, ASE_DIRECTION_SINK, args.serv[i].context, + i, 0, + args.serv.size()); + set_conn_info(&conn_info, args.serv[i].profile, + args.serv[i].context, ASE_DIRECTION_SINK); + printf("%s ERROR: context %d\n", __func__, args.serv[i].context); + conn_info.codec_qos_config_pair.push_back(codec_qos_config); + streams.push_back(conn_info); + } + if (args.serv[i].direction & ASE_DIRECTION_SRC) + { + set_codec_qos_config(&codec_qos_config_2, args.codecConfig, + &acs, ASE_DIRECTION_SRC, args.serv[i].context, + i, 1, + args.serv.size()); + set_conn_info(&conn_info, args.serv[i].profile, + args.serv[i].context, ASE_DIRECTION_SRC); + printf("%s ERROR: context %d\n", __func__, args.serv[i].context); + conn_info.codec_qos_config_pair.push_back(codec_qos_config_2); + streams.push_back(conn_info); + } + std::vector address; + address.push_back(bap_bd_addr); + sUcastClientInterface->Connect(address, true, streams); + } + while(!bapConnectionComplete) sleep(1); +} + +void do_bap_disconnect (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector streams; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 1 + }; + streams.push_back(type_1); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 2 + }; + streams.push_back(type_1); + } + sUcastClientInterface->Disconnect(bap_bd_addr, streams); + } +} + +void do_bap_start (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector streams; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 1 + }; + streams.push_back(type_1); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 2 + }; + streams.push_back(type_1); + } + sUcastClientInterface->Start(bap_bd_addr, streams); + } +} + +void do_bap_stop (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector streams; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 1 + }; + streams.push_back(type_1); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 2 + }; + streams.push_back(type_1); + } + sUcastClientInterface->Stop(bap_bd_addr, streams); + } +} + +void do_bap_disc_in_connecting (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_connect(p); + usleep(del *1000); + do_bap_disconnect(p); +} + +void do_bap_disc_in_starting (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_start(p); + usleep(del *1000); + do_bap_disconnect(p); +} + +void do_bap_disc_in_stopping (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_stop(p); + usleep(del *1000); + do_bap_disconnect(p); +} + +void do_bap_stop_in_starting (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_start(p); + usleep(del *1000); + do_bap_stop(p); +} + +void do_bap_update_stream (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector Update_Stream; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .direction = 1 + }; + bluetooth::bap::ucast::StreamUpdate sUpdate = + { + type_1, + bluetooth::bap::ucast::StreamUpdateType::STREAMING_CONTEXT, + args.serv[i].context + }; + Update_Stream.push_back(sUpdate); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .direction = 2 + }; + bluetooth::bap::ucast::StreamUpdate sUpdate = + { + type_1, + bluetooth::bap::ucast::StreamUpdateType::STREAMING_CONTEXT, + args.serv[i].context + }; + Update_Stream.push_back(sUpdate); + } + sUcastClientInterface->UpdateStream(bap_bd_addr, Update_Stream); + } +} + +void do_disable(char *p) +{ + bdt_disable(); +} + +void do_cleanup(char *p) +{ + bdt_cleanup(); +} + +void bdt_init(void) +{ + bdt_log("INIT BT "); + status = sBtInterface->init(&bt_callbacks, false, false, 0, nullptr, false); + sleep(1); + if (status == BT_STATUS_SUCCESS) { + status = sBtInterface->set_os_callouts(&bt_os_callbacks); + } + check_return_status(status); +} + +/****************************************************************************** +* + ** GATT SERVER API commands + +*******************************************************************************/ + +/* + * Main console command handler +*/ + +static void process_cmd(char *p, unsigned char is_job) +{ + char cmd[2048]; + int i = 0; + bt_pin_code_t pincode; + char *p_saved = p; + + get_str(&p, cmd); + + /* table commands */ + while (console_cmd_list[i].name != NULL) + { + if (is_cmd(console_cmd_list[i].name)) + { + if (!is_job && console_cmd_list[i].is_job) + create_cmdjob(p_saved); + else + { + console_cmd_list[i].handler(p); + } + return; + } + i++; + } + //pin key + if(cmd[6] == '.') { + for(i=0; i<6; i++) { + pincode.pin[i] = cmd[i]; + } + if(BT_STATUS_SUCCESS != sBtInterface->pin_reply(remote_bd_address, +TRUE, strlen((const char*)pincode.pin), &pincode)) { + printf("Pin Reply failed\n"); + } + //flush the char for pinkey + cmd[6] = 0; + } + else { + bdt_log("%s : unknown command\n", p_saved); + do_help(NULL); + } +} + +int main() +{ + config_permissions(); + bdt_log("\n:::::::::::::::::::::::::::::::::::::::::::::::::::"); + bdt_log(":: Bluedroid test app starting"); + + if ( HAL_load() < 0 ) { + perror("HAL failed to initialize, exit\n"); + unlink(PID_FILE); + exit(0); + } + + setup_test_env(); + + /* Automatically perform the init */ + bdt_init(); + sleep(5); + bdt_enable(); + sleep(5); + + sUcastClientInterface = (UcastClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_BAP_UCLIENT_ID); + sUcastClientInterface->Init(&sUcastClientCallbacks); + sPacsClientInterface = (PacsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_PACS_CLIENT_ID); + sPacsClientInterface->Init(&sPacsClientCallbacks); + sAscsClientInterface = (AscsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_ASCS_CLIENT_ID); + sAscsClientInterface->Init(&sAscsClientCallbacks); + + sleep(5); + while(!main_done) + { + char line[2048], *result; + + + /* command prompt */ + printf( ">" ); + fflush(stdout); + + if ((result = fgets (line, 2048, stdin)) == NULL) + { + printf("ERROR: The string is NULL. code %d\n", errno); + exit(0); + } + else + { + printf("UserInput\n"); + } + + if (line[0]!= '\0') + { + /* remove linefeed */ + line[strlen(line)-1] = 0; + + process_cmd(line, 0); + memset(line, '\0', 2048); + } + } + HAL_unload(); + + bdt_log(":: bap uca test app terminating"); + + return 0; +} + +int GetFileName(char *p, char *filename) +{ +// uint8_t i; + int len; + + skip_blanks(&p); + + printf("Input file name = %s\n", p); + + if (p == NULL) + { + printf("\nInvalid File Name... Please enter file name\n"); + return FALSE; + } + len = strlen(p); + + memcpy(filename, p, len); + filename[len] = '\0'; + + return TRUE; +} +uint8_t check_length(char *p) +{ + uint8_t val = 0; + while (*p != ' ' && *p != '\0') + { + val++; + p++; + } + return val; +} +int GetBdAddr(char *p, RawAddress* pbd_addr) +{ + char Arr[13] = {0}; + char *pszAddr = NULL; + uint8_t k1 = 0; + uint8_t k2 = 0; + uint8_t i; + + skip_blanks(&p); + + printf("Input=%s\n", p); + + if(12 != check_length(p)) + { + printf("\nInvalid Bd Address. Format[112233445566]\n"); + return FALSE; + } + memcpy(Arr, p, 12); + + for(i=0; i<12; i++) + { + Arr[i] = tolower(Arr[i]); + } + pszAddr = Arr; + + for(i=0; i<6; i++) + { + k1 = (uint8_t) ( (*pszAddr >= 'a') ? + ( 10 + (uint8_t)( *pszAddr - 'a' )) : (*pszAddr - '0') ); + pszAddr++; + k2 = (uint8_t) ( (*pszAddr >= 'a') ? + ( 10 + (uint8_t)( *pszAddr - 'a' )) : (*pszAddr - '0') ); + pszAddr++; + + if ( (k1>15)||(k2>15) ) + { + return FALSE; + } + pbd_addr->address[i] = (k1<<4 | k2); + } + return TRUE; +} +#endif //BAP_UNICAST_TEST_APP_INTERFACE diff --git a/le_audio/certification_tool/types/Android.bp b/le_audio/certification_tool/types/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..77d1630ba0b72ccc0014f741f615733d16bf2f98 --- /dev/null +++ b/le_audio/certification_tool/types/Android.bp @@ -0,0 +1,35 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +cc_library_static { + name: "libbluetooth-types-qti", + vendor_available: true, + enabled: false, + defaults: ["fluoride_types_defaults"], + cflags: [ + /* we export all classes, so change default visibility, instead of having EXPORT_SYMBOL on each class*/ + "-fvisibility=default", + ], + host_supported: true, + srcs: [ + "class_of_device.cc", + "raw_address.cc", + "bluetooth/uuid.cc", + ], +} diff --git a/le_audio/certification_tool/types/bluetooth/uuid.cc b/le_audio/certification_tool/types/bluetooth/uuid.cc new file mode 100644 index 0000000000000000000000000000000000000000..d05f4370f0caedfaf14a4ee6af5edcf2f357a167 --- /dev/null +++ b/le_audio/certification_tool/types/bluetooth/uuid.cc @@ -0,0 +1,177 @@ +/****************************************************************************** + * + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "uuid.h" + +#include +#include +#include +#include + +namespace bluetooth { + +static_assert(sizeof(Uuid) == 16, "Uuid must be 16 bytes long!"); + +using UUID128Bit = Uuid::UUID128Bit; + +const Uuid Uuid::kEmpty = Uuid::From128BitBE(UUID128Bit{{0x00}}); + +namespace { +constexpr Uuid kBase = Uuid::From128BitBE( + UUID128Bit{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}}); +} // namespace + +size_t Uuid::GetShortestRepresentationSize() const { + if (memcmp(uu.data() + kNumBytes32, kBase.uu.data() + kNumBytes32, + kNumBytes128 - kNumBytes32) != 0) { + return kNumBytes128; + } + + if (uu[0] == 0 && uu[1] == 0) return kNumBytes16; + + return kNumBytes32; +} + +bool Uuid::Is16Bit() const { + return GetShortestRepresentationSize() == kNumBytes16; +} + +uint16_t Uuid::As16Bit() const { return (((uint16_t)uu[2]) << 8) + uu[3]; } + +uint32_t Uuid::As32Bit() const { + return (((uint32_t)uu[0]) << 24) + (((uint32_t)uu[1]) << 16) + + (((uint32_t)uu[2]) << 8) + uu[3]; +} + +Uuid Uuid::FromString(const std::string& uuid, bool* is_valid) { + if (is_valid) *is_valid = false; + Uuid ret = kBase; + + if (uuid.empty()) return ret; + + uint8_t* p = ret.uu.data(); + if (uuid.size() == kString128BitLen) { + if (uuid[8] != '-' || uuid[13] != '-' || uuid[18] != '-' || + uuid[23] != '-') { + return ret; + } + + int c; + int rc = + sscanf(uuid.c_str(), + "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx" + "-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%n", + &p[0], &p[1], &p[2], &p[3], &p[4], &p[5], &p[6], &p[7], &p[8], + &p[9], &p[10], &p[11], &p[12], &p[13], &p[14], &p[15], &c); + if (rc != 16) return ret; + if (c != kString128BitLen) return ret; + + if (is_valid) *is_valid = true; + } else if (uuid.size() == 8) { + int c; + int rc = sscanf(uuid.c_str(), "%02hhx%02hhx%02hhx%02hhx%n", &p[0], &p[1], + &p[2], &p[3], &c); + if (rc != 4) return ret; + if (c != 8) return ret; + + if (is_valid) *is_valid = true; + } else if (uuid.size() == 4) { + int c; + int rc = sscanf(uuid.c_str(), "%02hhx%02hhx%n", &p[2], &p[3], &c); + if (rc != 2) return ret; + if (c != 4) return ret; + + if (is_valid) *is_valid = true; + } + + return ret; +} + +Uuid Uuid::From16Bit(uint16_t uuid16) { + Uuid u = kBase; + + u.uu[2] = (uint8_t)((0xFF00 & uuid16) >> 8); + u.uu[3] = (uint8_t)(0x00FF & uuid16); + return u; +} + +Uuid Uuid::From32Bit(uint32_t uuid32) { + Uuid u = kBase; + + u.uu[0] = (uint8_t)((0xFF000000 & uuid32) >> 24); + u.uu[1] = (uint8_t)((0x00FF0000 & uuid32) >> 16); + u.uu[2] = (uint8_t)((0x0000FF00 & uuid32) >> 8); + u.uu[3] = (uint8_t)(0x000000FF & uuid32); + return u; +} + +Uuid Uuid::From128BitBE(const uint8_t* uuid) { + UUID128Bit tmp; + memcpy(tmp.data(), uuid, kNumBytes128); + return From128BitBE(tmp); +} + +Uuid Uuid::From128BitLE(const UUID128Bit& uuid) { + Uuid u; + std::reverse_copy(uuid.data(), uuid.data() + kNumBytes128, u.uu.begin()); + return u; +} + +Uuid Uuid::From128BitLE(const uint8_t* uuid) { + UUID128Bit tmp; + memcpy(tmp.data(), uuid, kNumBytes128); + return From128BitLE(tmp); +} + +const UUID128Bit Uuid::To128BitLE() const { + UUID128Bit le; + std::reverse_copy(uu.data(), uu.data() + kNumBytes128, le.begin()); + return le; +} + +const UUID128Bit& Uuid::To128BitBE() const { return uu; } + +Uuid Uuid::GetRandom() { + Uuid uuid; + base::RandBytes(uuid.uu.data(), uuid.uu.size()); + return uuid; +} + +bool Uuid::IsEmpty() const { return *this == kEmpty; } + +void Uuid::UpdateUuid(const Uuid& uuid) { + uu = uuid.uu; +} + +bool Uuid::operator<(const Uuid& rhs) const { + return std::lexicographical_compare(uu.begin(), uu.end(), rhs.uu.begin(), + rhs.uu.end()); +} + +bool Uuid::operator==(const Uuid& rhs) const { return uu == rhs.uu; } + +bool Uuid::operator!=(const Uuid& rhs) const { return uu != rhs.uu; } + +std::string Uuid::ToString() const { + return base::StringPrintf( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uu[0], uu[1], uu[2], uu[3], uu[4], uu[5], uu[6], uu[7], uu[8], uu[9], + uu[10], uu[11], uu[12], uu[13], uu[14], uu[15]); +} +} // namespace bluetooth diff --git a/le_audio/certification_tool/types/bluetooth/uuid.h b/le_audio/certification_tool/types/bluetooth/uuid.h new file mode 100644 index 0000000000000000000000000000000000000000..0fe5b5bd1e4b76a88e35c6e7768d80fc5cfcc0ab --- /dev/null +++ b/le_audio/certification_tool/types/bluetooth/uuid.h @@ -0,0 +1,143 @@ +/****************************************************************************** + * + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace bluetooth { + +// This class is representing Bluetooth UUIDs across whole stack. +// Here are some general endianness rules: +// 1. UUID is internally kept as as Big Endian. +// 2. Bytes representing UUID coming from upper layers, Java or Binder, are Big +// Endian. +// 3. Bytes representing UUID coming from lower layer, HCI packets, are Little +// Endian. +// 4. UUID in storage is always string. +class Uuid final { + public: + static constexpr size_t kNumBytes128 = 16; + static constexpr size_t kNumBytes32 = 4; + static constexpr size_t kNumBytes16 = 2; + + static constexpr size_t kString128BitLen = 36; + + static const Uuid kEmpty; // 00000000-0000-0000-0000-000000000000 + + using UUID128Bit = std::array; + + Uuid() = default; + + // Creates and returns a random 128-bit UUID. + static Uuid GetRandom(); + + // Returns the shortest possible representation of this UUID in bytes. Either + // kNumBytes16, kNumBytes32, or kNumBytes128 + size_t GetShortestRepresentationSize() const; + + // Returns true if this UUID can be represented as 16 bit. + bool Is16Bit() const; + + // Returns 16 bit Little Endian representation of this UUID. Use + // GetShortestRepresentationSize() or Is16Bit() before using this method. + uint16_t As16Bit() const; + + // Returns 32 bit Little Endian representation of this UUID. Use + // GetShortestRepresentationSize() before using this method. + uint32_t As32Bit() const; + + // Converts string representing 128, 32, or 16 bit UUID in + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, xxxxxxxx, or xxxx format to UUID. If + // set, optional is_valid parameter will be set to true if conversion is + // successfull, false otherwise. + static Uuid FromString(const std::string& uuid, bool* is_valid = nullptr); + + // Converts 16bit Little Endian representation of UUID to UUID + static Uuid From16Bit(uint16_t uuid16bit); + + // Converts 32bit Little Endian representation of UUID to UUID + static Uuid From32Bit(uint32_t uuid32bit); + + // Converts 128 bit Big Endian array representing UUID to UUID. + static constexpr Uuid From128BitBE(const UUID128Bit& uuid) { + Uuid u(uuid); + return u; + } + + // Converts 128 bit Big Endian array representing UUID to UUID. |uuid| points + // to beginning of array. + static Uuid From128BitBE(const uint8_t* uuid); + + // Converts 128 bit Little Endian array representing UUID to UUID. + static Uuid From128BitLE(const UUID128Bit& uuid); + + // Converts 128 bit Little Endian array representing UUID to UUID. |uuid| + // points to beginning of array. + static Uuid From128BitLE(const uint8_t* uuid); + + // Returns 128 bit Little Endian representation of this UUID + const UUID128Bit To128BitLE() const; + + // Returns 128 bit Big Endian representation of this UUID + const UUID128Bit& To128BitBE() const; + + // Returns string representing this UUID in + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format, lowercase. + std::string ToString() const; + + // Returns true if this UUID is equal to kEmpty + bool IsEmpty() const; + + // Update UUID with new value + void UpdateUuid(const Uuid& uuid); + + bool operator<(const Uuid& rhs) const; + bool operator==(const Uuid& rhs) const; + bool operator!=(const Uuid& rhs) const; + + private: + constexpr Uuid(const UUID128Bit& val) : uu{val} {}; + + // Network-byte-ordered ID (Big Endian). + UUID128Bit uu; +}; +} // namespace bluetooth + +inline std::ostream& operator<<(std::ostream& os, const bluetooth::Uuid& a) { + os << a.ToString(); + return os; +} + +// Custom std::hash specialization so that bluetooth::UUID can be used as a key +// in std::unordered_map. +namespace std { + +template <> +struct hash { + std::size_t operator()(const bluetooth::Uuid& key) const { + const auto& uuid_bytes = key.To128BitBE(); + std::hash hash_fn; + return hash_fn(std::string(reinterpret_cast(uuid_bytes.data()), + uuid_bytes.size())); + } +}; + +} // namespace std diff --git a/le_audio/certification_tool/types/class_of_device.cc b/le_audio/certification_tool/types/class_of_device.cc new file mode 100644 index 0000000000000000000000000000000000000000..775a412a35096292e9963d1b4488070b6ade0cf5 --- /dev/null +++ b/le_audio/certification_tool/types/class_of_device.cc @@ -0,0 +1,78 @@ +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "class_of_device.h" + +#include +#include +#include +#include +#include + +static_assert(sizeof(ClassOfDevice) == ClassOfDevice::kLength, + "ClassOfDevice must be 3 bytes long!"); + +ClassOfDevice::ClassOfDevice(const uint8_t (&class_of_device)[kLength]) { + std::copy(class_of_device, class_of_device + kLength, cod); +}; + +std::string ClassOfDevice::ToString() const { + return base::StringPrintf("%03x-%01x-%02x", + (static_cast(cod[2]) << 4) | cod[1] >> 4, + cod[1] & 0x0f, cod[0]); +} + +bool ClassOfDevice::FromString(const std::string& from, ClassOfDevice& to) { + ClassOfDevice new_cod; + if (from.length() != 8) return false; + + std::vector byte_tokens = + base::SplitString(from, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (byte_tokens.size() != 3) return false; + if (byte_tokens[0].length() != 3) return false; + if (byte_tokens[1].length() != 1) return false; + if (byte_tokens[2].length() != 2) return false; + + uint16_t values[3]; + + for (size_t i = 0; i < kLength; i++) { + const auto& token = byte_tokens[i]; + + char* temp = nullptr; + values[i] = strtol(token.c_str(), &temp, 16); + if (*temp != '\0') return false; + } + + new_cod.cod[0] = values[2]; + new_cod.cod[1] = values[1] | ((values[0] & 0xf) << 4); + new_cod.cod[2] = values[0] >> 4; + + to = new_cod; + return true; +} + +size_t ClassOfDevice::FromOctets(const uint8_t* from) { + std::copy(from, from + kLength, cod); + return kLength; +}; + +bool ClassOfDevice::IsValid(const std::string& cod) { + ClassOfDevice tmp; + return ClassOfDevice::FromString(cod, tmp); +} diff --git a/le_audio/certification_tool/types/class_of_device.h b/le_audio/certification_tool/types/class_of_device.h new file mode 100644 index 0000000000000000000000000000000000000000..b0fffc88532721f78f6d0a363d748eef1c67cfd3 --- /dev/null +++ b/le_audio/certification_tool/types/class_of_device.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include + +namespace bluetooth { +namespace types { + +/** Bluetooth Class of Device */ +class ClassOfDevice final { + public: + static constexpr unsigned int kLength = 3; + + uint8_t cod[kLength]; + + ClassOfDevice() = default; + ClassOfDevice(const uint8_t (&class_of_device)[kLength]); + + bool operator==(const ClassOfDevice& rhs) const { + return (std::memcmp(cod, rhs.cod, sizeof(cod)) == 0); + } + + std::string ToString() const; + + // Converts |string| to ClassOfDevice and places it in |to|. If |from| does + // not represent a Class of Device, |to| is not modified and this function + // returns false. Otherwise, it returns true. + static bool FromString(const std::string& from, ClassOfDevice& to); + + // Copies |from| raw Class of Device octets to the local object. + // Returns the number of copied octets (always ClassOfDevice::kLength) + size_t FromOctets(const uint8_t* from); + + static bool IsValid(const std::string& class_of_device); +}; + +inline std::ostream& operator<<(std::ostream& os, const ClassOfDevice& c) { + os << c.ToString(); + return os; +} + +} // namespace types +} // namespace bluetooth + +using ::bluetooth::types::ClassOfDevice; // TODO, remove diff --git a/le_audio/certification_tool/types/raw_address.cc b/le_audio/certification_tool/types/raw_address.cc new file mode 100644 index 0000000000000000000000000000000000000000..8369f5f36af260667b2dcd612110d808b660e330 --- /dev/null +++ b/le_audio/certification_tool/types/raw_address.cc @@ -0,0 +1,73 @@ +/******************************************************************************* + * + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "raw_address.h" + +#include +#include +#include +#include +#include + +static_assert(sizeof(RawAddress) == 6, "RawAddress must be 6 bytes long!"); + +const RawAddress RawAddress::kAny{{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; +const RawAddress RawAddress::kEmpty{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +RawAddress::RawAddress(const uint8_t (&addr)[6]) { + std::copy(addr, addr + kLength, address); +} + +std::string RawAddress::ToString() const { + return base::StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", address[0], + address[1], address[2], address[3], address[4], + address[5]); +} + +bool RawAddress::FromString(const std::string& from, RawAddress& to) { + RawAddress new_addr; + if (from.length() != 17) return false; + + std::vector byte_tokens = + base::SplitString(from, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (byte_tokens.size() != 6) return false; + + for (int i = 0; i < 6; i++) { + const auto& token = byte_tokens[i]; + + if (token.length() != 2) return false; + + char* temp = nullptr; + new_addr.address[i] = strtol(token.c_str(), &temp, 16); + if (*temp != '\0') return false; + } + + to = new_addr; + return true; +} + +size_t RawAddress::FromOctets(const uint8_t* from) { + std::copy(from, from + kLength, address); + return kLength; +}; + +bool RawAddress::IsValidAddress(const std::string& address) { + RawAddress tmp; + return RawAddress::FromString(address, tmp); +} diff --git a/le_audio/certification_tool/types/raw_address.h b/le_audio/certification_tool/types/raw_address.h new file mode 100644 index 0000000000000000000000000000000000000000..ca750181b6c21c65d84d86001efcee501c50335d --- /dev/null +++ b/le_audio/certification_tool/types/raw_address.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +/** Bluetooth Address */ +class RawAddress final { + public: + static constexpr unsigned int kLength = 6; + + uint8_t address[kLength]; + + RawAddress() = default; + RawAddress(const uint8_t (&addr)[kLength]); + + bool operator<(const RawAddress& rhs) const { + return (std::memcmp(address, rhs.address, sizeof(address)) < 0); + } + bool operator==(const RawAddress& rhs) const { + return (std::memcmp(address, rhs.address, sizeof(address)) == 0); + } + bool operator>(const RawAddress& rhs) const { return (rhs < *this); } + bool operator<=(const RawAddress& rhs) const { return !(*this > rhs); } + bool operator>=(const RawAddress& rhs) const { return !(*this < rhs); } + bool operator!=(const RawAddress& rhs) const { return !(*this == rhs); } + + bool IsEmpty() const { return *this == kEmpty; } + + std::string ToString() const; + + // Converts |string| to RawAddress and places it in |to|. If |from| does + // not represent a Bluetooth address, |to| is not modified and this function + // returns false. Otherwise, it returns true. + static bool FromString(const std::string& from, RawAddress& to); + + // Copies |from| raw Bluetooth address octets to the local object. + // Returns the number of copied octets - should be always RawAddress::kLength + size_t FromOctets(const uint8_t* from); + + static bool IsValidAddress(const std::string& address); + + static const RawAddress kEmpty; // 00:00:00:00:00:00 + static const RawAddress kAny; // FF:FF:FF:FF:FF:FF +}; + +inline std::ostream& operator<<(std::ostream& os, const RawAddress& a) { + os << a.ToString(); + return os; +} + +template <> +struct std::hash { + std::size_t operator()(const RawAddress& val) const { + static_assert(sizeof(uint64_t) >= RawAddress::kLength); + uint64_t int_addr = 0; + memcpy(reinterpret_cast(&int_addr), val.address, + RawAddress::kLength); + return std::hash{}(int_addr); + } +}; diff --git a/le_audio/frameworks/base/Android.bp b/le_audio/frameworks/base/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..1d9cf9f9501b2869b77904348cf7ab8cf2086033 --- /dev/null +++ b/le_audio/frameworks/base/Android.bp @@ -0,0 +1,22 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +filegroup { + name: "framework-bluetooth-adva-srcs", + srcs: ["core/**/*.java",], +} diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..bbb5a3007287523ee4c555144badce4fda7102ae --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java @@ -0,0 +1,178 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package android.bluetooth; + +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.ScanResult; +import android.annotation.IntDef; +import java.util.Map; +import java.lang.String; +import java.lang.Integer; +import java.util.List; + + + +/** + * Bluetooth LE Broadcast Scan Assistance related callbacks, used to deliver result of + * Broadcast Assist operations performed using {@link BleBroadcastAudioScanAssistManager} + * + * @hide + * @see BleBroadcastAudioScanAssistManager + */ +public abstract class BleBroadcastAudioScanAssistCallback { + + /** @hide */ + @IntDef(prefix = "BASS_STATUS_", value = { + BASS_STATUS_SUCCESS, + BASS_STATUS_FAILURE, + BASS_STATUS_FATAL, + BASS_STATUS_TXN_TIMEOUT, + BASS_STATUS_INVALID_SOURCE_ID, + BASS_STATUS_COLOCATED_SRC_UNAVAILABLE, + BASS_STATUS_INVALID_SOURCE_SELECTED, + BASS_STATUS_SOURCE_UNAVAILABLE, + BASS_STATUS_DUPLICATE_ADDITION, + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface Bass_Status {} + + public static final int BASS_STATUS_SUCCESS = 0x00; + public static final int BASS_STATUS_FAILURE = 0x01; + public static final int BASS_STATUS_FATAL = 0x02; + public static final int BASS_STATUS_TXN_TIMEOUT = 0x03; + + public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04; + public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05; + public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06; + public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07; + public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08; + public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09; + public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10; + + /** + * Callback when BLE broadcast audio source found. + * result of {@link BleBroadcastAudioScanAssistManager#searchforLeAudioBroadcasters} will be + * delivered through this callback + * + * @param scanres {@link ScanResult} object of the scanned result + */ + public void onBleBroadcastSourceFound(ScanResult scanres) { + }; + + + /** + * Callback when BLE broadcast audio source found. + * result of {@link BleBroadcastAudioScanAssistManager#searchforLeAudioBroadcasters} will be + * delivered through this callback + * + * @param status Status of the Broadcast source selection. + * @param broadcastSourceChannels {@link BleBroadcastSourceChannel} List + * containing avaiable broadcast source channels that are being broadcasted from the selected + * broadcast source + * + */ + public void onBleBroadcastSourceSelected(BluetoothDevice device, + @Bass_Status int status, + List broadcastSourceChannels) { + }; + + /** + * Callback when BLE broadcast audio source is been successfully added to the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the source information added + * to the Scan delegator. Actual updated source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent + * + * @param device remote scan delegator for which Source is been added. + * @param srcId source Id of the Broadcast source information added + * @param status true on succesful addition of source Information, false otherwise. + * + */ + public void onBleBroadcastAudioSourceAdded(BluetoothDevice device, + byte srcId, + @Bass_Status int status) { + }; + + /** + * Callback when BLE broadcast audio source Information is been updated to the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#updateBroadcastSource} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the source information update request is succesfully + * written on the Scan delegator. Actual updated source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent + * + * @param device remote scan delegator for which Source is been updated. + * @param srcId source Id of the Broadcast source information updated. + * @param status true on succesful updating of source Information, false otherwise. + * + */ + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice device, + byte srcId, + @Bass_Status int status) { + }; + + /** + * Callback when BLE broadcast audio source Information is updated with broadcast PIN code to the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#setBroadcastCode} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the Broadcast PIN update request is succesfully + * written to the Scan delegator. Actual updated source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent. + * Encryption status from the {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} will + * confirm the succesfull Broadcast PIN code and resulting decryption of the Broadcast data at the reciver side. + * + * @param device remote scan delegator for which Source is been updated. + * @param srcId source Id of the Broadcast PIN updated. + * @param status true on succesful updating of source Information, false otherwise. + * + */ + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + @Bass_Status int status) { + }; + + /** + * Callback when BLE broadcast audio source Information is removed from the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#removeBroadcastSource} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the Broadcast source infor removal request is succesfully + * written to the Scan delegator. Actual removal of source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent. + * Deletion of source Information will result is setting all the source information attributes to ZERO other than + * source Id + * + * @param device remote scan delegator for which Source is removed. + * @param srcId source Id of the Broadcast source information removed. + * @param status true on succesful updating of source Information, false otherwise. + * + */ + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + @Bass_Status int status) { + }; +} diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java new file mode 100644 index 0000000000000000000000000000000000000000..c16bcb6fe0d5a66154edaee7b2c0cb927f3bd239 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java @@ -0,0 +1,635 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package android.bluetooth; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.IntDef; +import android.annotation.SdkConstant.SdkConstantType; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothAdapter.LeScanCallback; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import java.io.InvalidClassException; +import android.os.DeadObjectException; +import android.util.Log; +import android.content.Context; +import java.util.UUID; +import android.os.ParcelUuid; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; +import java.util.Objects; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.BleBroadcastAudioScanAssistManager; + +import android.os.SystemProperties; + +/** + * This class provides methods to perform Broadcast Assistance related + * operations. + *

+ * Use {@link BleBroadcastAudioScanAssistManager()} to get an + * instance of {@link BleBroadcastAudioScanAssistManager}. + *

+ * Note: Most of the methods here require + * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @hide + */ +public final class BleBroadcastAudioScanAssistManager { + + private static final String TAG = "BleBroadcastAudioScanAssistManager"; + private static final boolean DBG = true; + private static final boolean VDBG = true; + + /** @hide */ + @IntDef(prefix = "SYNC_", value = { + SYNC_METADATA, + SYNC_AUDIO, + SYNC_METADATA_AUDIO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistSyncState {} + /** + * Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method + * where Application wants to synchronize only to Metadata (i.e. Only Periodic advs) and not to + * Broadcsat audio stream (i.e. BIS )from broadcast source + */ + public static final int SYNC_METADATA = 0; + /** + * Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method + * where Application wants to synchronize only to Broadcast Audio stream (i.e. BIS) and not to + Metadata (i.e. Periodic advs )from broadcast source + */ + public static final int SYNC_AUDIO = 1; + /** + * Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method + * where Application wants to synchronize to both Broadcast Audio stream (i.e. BIS) and also to + * Metadata (i.e. Periodic advs )from broadcast source + */ + public static final int SYNC_METADATA_AUDIO = 2; + + private BluetoothAdapter mBluetoothAdapter; + BleBroadcastAudioScanAssistCallback mAppCallback; + BluetoothDevice mBluetoothDevice; + int mSyncState = SYNC_METADATA; + BluetoothSyncHelper mBluetoothSyncHelper = null; + BleBroadcastSourceInfo mBroadcastAudioSourceInfo = null; + private byte INVALID_SOURCE_ID = -1; + + /** + * Intent used to broadcast the "Broadcast receiver State" information of a Scan delegator device. + * Whenever there is a change in Broadcast source Information stored at Scan delegator device + * this Itent will be delivered to Application layer + * + * {@link #BluetoothSyncHelper} profile need to be connected to the Scan delegator device + * to get these notifications + * + *

This intent will have two extra: + *

    + *
  • {@link BluetoothDevice#EXTRA_DEVICE} - The remote device for which broadcast reciver + * state information is broadcasted. It can + * be null if no device is active.
  • + *
+ *
    + *
  • {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO} - The BleBroadcastSourceInfo Object + * having information Broadcast receiver state
  • + *
+ *
    + *
  • {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO_INDEX} - Index of the BleBroadcastSourceInfo + * object broadcasted
  • + *
+ *
    + *
  • {@link BleBroadcastSourceInfo#EXTRA_MAX_NUM_SOURCE_INFOS} - Maximum number of source Informations + * that this Broadcast receiver can hold
  • + *
+ * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_SOURCE_INFO = + "android.bluetooth.BroadcastAudioSAManager.action.BROADCAST_SOURCE_INFO"; + + + // These callbacks run on the main thread. + private final class BassclientServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + log(TAG, "BassService connected"); + onBluetoothSyncHelperStateChanged(true, proxy); + + } + + public void onServiceDisconnected(int profile) { + log(TAG, "BassService disconnected"); + onBluetoothSyncHelperStateChanged(false, null); + } + } + + private void onBluetoothSyncHelperStateChanged(boolean on, BluetoothProfile proxy) { + if (on) { + mBluetoothSyncHelper = (BluetoothSyncHelper) proxy; + mBluetoothSyncHelper.registerAppCallback(mBluetoothDevice, mAppCallback); + this.notifyAll(); + } else { + mBluetoothSyncHelper = null; + } + } + + /*package*/BleBroadcastAudioScanAssistManager(BluetoothSyncHelper scanOffloader, BluetoothDevice device, + BleBroadcastAudioScanAssistCallback callback + ) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + mAppCallback = callback; + mBluetoothDevice = device; + mBluetoothSyncHelper = scanOffloader; + } + + + /*finalize method to cleanup*/ + protected void finalize() { + log(TAG, "finalize()"); + if (mBluetoothSyncHelper != null) { + mBluetoothSyncHelper.unregisterAppCallback(mBluetoothDevice, mAppCallback); + } + } + + /** + * Search for Le Audio Broadcasters on behalf of the Scan delegator with which this + * {@ BleBroadcastAudioScanAssistManager} is instantiated + * + * search results will be delivered to application using + * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound} + * + * @return returns true if It is successfully initiated the Search for Audio broadcasters, + * false otherwise + * @hide + */ + public boolean searchforLeAudioBroadcasters () { + log(TAG, "searchforLeAudioBroadcasters: "); + if (mBluetoothSyncHelper != null) { + return mBluetoothSyncHelper.searchforLeAudioBroadcasters(mBluetoothDevice); + } else { + Log.e(TAG, "searchforLeAudioBroadcasters: mBluetoothSyncHelper is null"); + } + return false; + } + /** + * Stops an ongoing Bluetooth LE Search for Audio Broadcasters. + * + * @return returns true if It is successfully initiated the Stopped the Search for Audio broadcasters + * false otherwise + * + *@hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean stopSearchforLeAudioBroadcasters() { + log(TAG, "stopSearchforLeAudioBroadcasters()"); + if (mBluetoothSyncHelper != null) { + return mBluetoothSyncHelper.stopSearchforLeAudioBroadcasters(mBluetoothDevice); + } else { + Log.e(TAG, "stopSearchforLeAudioBroadcasters: mBluetoothSyncHelper is null"); + } + return false; + + } + + /* Internal helper function to convert user input sync state to required internal + * format + */ + private int convertMetadataSyncState(int syncState) { + if (syncState == SYNC_METADATA_AUDIO || syncState == SYNC_METADATA) { + return BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC; + } + return BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IDLE; + } + + /* Internal helper function to convert user input sync state to required internal + * format + */ + private int convertAudioDataSyncState(int syncState) { + if (syncState == SYNC_METADATA_AUDIO || syncState == SYNC_AUDIO) { + return BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED; + } else { + Log.e(TAG, "searchforLeAudioBroadcasters: mBluetoothSyncHelper is null"); + } + return BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED; + } + + /** + * Selects broadcast source for the Scan delegator. This internally performs Periodic + * synchronization to the given Broadcast source device, upon acquision of Synchronization information, + * It will be notified with avaiable Broadcast source channels that can be synchronized in the remote + * device. + * Application should select set of Broadcast channels that need to be synchronized and follow up + * with a call to {@link #addBroadcastSource} operation + * + * Result of selction of Broadcast source will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#OnBroadcastAudioSourceSelected} + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. Select broadcast source operation will be performed on behalf of + * all the Coordinated set devices + * + * + * @param ScanResult {@link #ScanResult} of the Broadcasting source, + * this is the result obtained from the {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound} + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members + * + * @return returns true if It is successfully initiated select Broadcast source operation + * false otherwise + * @hide + */ + public boolean selectBroadcastSource(ScanResult scanRes, boolean isGroupOp) { + if (scanRes == null) { + Log.e(TAG, "selectBroadcastSource: Invalid scan res"); + return false; + } + log(TAG, "selectBroadcastSource: " + scanRes); + if (mBluetoothSyncHelper != null) { + return mBluetoothSyncHelper.selectBroadcastSource(mBluetoothDevice, scanRes, isGroupOp); + } else { + Log.e(TAG, "selectBroadcastSource: mBluetoothSyncHelper is null"); + } + return false; + } + + + private boolean isValidBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { + boolean ret = true; + List currentSourceInfos = + mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice); + if (currentSourceInfos == null) { + Log.e(TAG, "no source info details for remote"); + ret = false; + } else { + for (int i=0; i currentSourceInfos = + mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice); + if (currentSourceInfos == null) { + retVal = false; + } else { + for (int i=0; i selectedBISIndicies) { + if (selectedBISIndicies == null) { + log(TAG, "printSelectedIndicies : no selected indicies"); + return; + } + for (int i=0; i selectedBroadcastChannels, + boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "addBroadcastSource: no BluetoothSyncHelper handle"); + return false; + } + + if (syncState != SYNC_METADATA && + syncState != SYNC_METADATA_AUDIO) { + log(TAG, "addBroadcastSource: Invalid syncState" + syncState); + return false; + } + printSelectedIndicies(selectedBroadcastChannels); + int metadataSyncState = -1; + int audioSyncState = -1; + mSyncState = syncState; + metadataSyncState = convertMetadataSyncState (mSyncState); + audioSyncState = convertAudioDataSyncState(mSyncState); + if (mBroadcastAudioSourceInfo == null) { + //all of these will be overriden at service layer later + mBroadcastAudioSourceInfo = new BleBroadcastSourceInfo( + audioSource, + (byte)0xBB, /*advSid*/ + BleBroadcastSourceInfo.BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC, + metadataSyncState, + audioSyncState, + selectedBroadcastChannels); + if (mBroadcastAudioSourceInfo == null) { + Log.e(TAG, "addBroadcastSource: mBroadcastAudioSourceInfo instantiated failure"); + return false; + } + } + if(isValidBroadcastSourceInfo(mBroadcastAudioSourceInfo)) { + mBluetoothSyncHelper.addBroadcastSource(mBluetoothDevice, + mBroadcastAudioSourceInfo, + isGroupOp + ); + } else { + log(TAG, "Similar source information already exists"); + return false; + } + return true; + } + /** + * Updates a broadcast source information in the Scan delegator. + * It will be written on to the Scan delegator's Characteristics + * + * Result of updating of Broadcast source to the scan delegator will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceUpdated} + * + * However Successful updating of Broadcast source information will be indicated through Broadcast reciver state information + * update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. Update broadcast source operation will be performed on behalf of + * all the Coordinated set devices + * + * Same Broadcast source Information change will be written on to all the members of Coordinated set and + * PAST will be performed based on the request from remote. + * + * In case of Group Operation, If there are no matching source Information present in any of coordinated set members, + * Update Broadcast source opeation will be failed and result will notified through + * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceUpdated} + * + * @param sourceId sourceId of the Broadcast Source information which need to be updated + * @param syncState can be one of {@link #SYNC_METADATA}, + * {@link #SYNC_AUDIO}, {@link #SYNC_METADATA_AUDIO} + * + * @param selectedBroadcastChannels is a List of Broadcast channels that need to be synchronized with the given broadcast audio source + * from Avaialble Broadcast indicies. + * Avaiable broadcast indicies are notified application using {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceSelected} + * BroadcastSourceChannel.mStatus set to be TRUE or FALSE based on the need of synchronization. + * + * null value of selectedBroadcastChannels resulting in syncing to all avaialble Broadcast channels. + * check {@link BleurceChannel} for more information + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members, False otherwise + * + * @return returns true if It is successfully initiated update Broadcast source information + * operation + * false otherwise + * @hide + */ + public boolean updateBroadcastSource (byte sourceId, int syncState, + List selectedBroadcastChannels, + boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "updateBroadcastSource: no BluetoothSyncHelper handle"); + return false; + } + if (isValidSourceId(sourceId) == false) { + log(TAG, "updateBroadcastSource: Invalid source Id"); + return false; + } + int audioSyncState = -1; + int metadataSyncState = -1; + log(TAG, "updateBroadcastSource: sourceId" + sourceId + ", syncState:" + syncState); + + mSyncState = syncState; + metadataSyncState = convertMetadataSyncState (mSyncState); + audioSyncState = convertAudioDataSyncState(mSyncState); + + printSelectedIndicies(selectedBroadcastChannels); + + log(TAG, "updateBroadcastSource: audioSyncState:" + audioSyncState); + log(TAG, "updateBroadcastSource: metadataSyncState:" + metadataSyncState); + + BleBroadcastSourceInfo sourceInfo = new BleBroadcastSourceInfo(sourceId); + if (sourceInfo != null) { + sourceInfo.setMetadataSyncState(metadataSyncState); + sourceInfo.setAudioSyncState(audioSyncState); + sourceInfo.setSourceId(sourceId); + sourceInfo.setBroadcastChannelsSyncStatus(selectedBroadcastChannels); + } else { + Log.e(TAG, "updateBroadcastSource: sourceInfo not created"); + return false; + } + return mBluetoothSyncHelper.updateBroadcastSource(mBluetoothDevice, + sourceInfo, + isGroupOp); + } + /** + * Sets the Broadcast pin code to the Scan delegator so that It can decrypt + * the synchronized audio at the reciver side + * + * It will be written on to the Scan delegator's Characteristics. + * Result of Setting of Broadcast PIN code to the scan delegator will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated} + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. set Broadcast PIN operation will be performed on all the Coordinated set devices + * + * Same Broadcast PIN code will be written on to all the members of Coordinated set and + * on the request from remote. + * + * In case of Group Operation, If there are no matching source Information(BD address, adv instance) + * present in any of coordinated set members, + * Set Broadcast PIN opeation will be failed and result will notified through + * {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated} + * + * + * However, Successful updating of Broadcast PIN code will be indicated through Broadcast reciver state information + * update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent. + * + * @param sourceId sourceId of the Broadcast Source information which need to be updated + * @param broadcastCode is the String of maximum 16 characters in length + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members, False otherwise + * + * @return returns true if It is successfully initiated set Broadcast code operation + * false otherwise + * @hide + */ + public boolean setBroadcastCode (byte sourceId, String broadcastCode, boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "setBroadcastCode: no BluetoothSyncHelper handle"); + return false; + } + if (isValidSourceId(sourceId) == false) { + log(TAG, "setBroadcastCode: Invalid source Id"); + return false; + } + + log(TAG, "setBroadcastCode: " + "sourceId:" + + sourceId + "BroadcastCode:" + broadcastCode); + BleBroadcastSourceInfo sourceInfo = new BleBroadcastSourceInfo(sourceId); + if (sourceInfo != null) { + sourceInfo.setSourceId(sourceId); + sourceInfo.setBroadcastCode(broadcastCode); + } else { + Log.e(TAG, "setBroadcastCode: sourceInfo not created"); + return false; + } + return mBluetoothSyncHelper.setBroadcastCode(mBluetoothDevice, + sourceInfo, + isGroupOp); + } + /** + * Removes the Broadcast Source Information from the Scan delegator + * It will be written on to the "Scan delegators" Characteristics + * + * Result of removal of Broadcast source to the scan delegator will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#OnBroadcastAudioSourRemoved} + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. remove broadcast operation will be performed on all the Coordinated set devices + * + * Remove Broadcast will be performed on to all the members of Coordinated set + * + * In case of Group Operation, If there are no matching source Information(BD address, adv instance) + * present in any of coordinated set members. + * + * Set Broadcast PIN opeation will be failed and result will notified through + * {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated} + * Successful removal of Brocast source information will be indicated through + * Broadcast receiver state Information through + * {@link #ACTION_BROADCAST_RECEIVER_STATE} intent + * + * @param sourceId sourceId of the Broadcast Source information which need to be updated + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members, False otherwise + * + * @return returns true if It is successfully initiated remove broadcast source operation + * false otherwise + * @hide + */ + public boolean removeBroadcastSource (byte sourceId, boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "removeBroadcastSource: no BluetoothSyncHelper handle"); + return false; + } + if (isValidSourceId(sourceId) == false) { + log(TAG, "removeBroadcastSource: Invalid source Id"); + return false; + } + log(TAG, "removeBroadcastSource: sourceId" + sourceId); + + return mBluetoothSyncHelper.removeBroadcastSource(mBluetoothDevice, + sourceId, + isGroupOp); + } + /** + * Get all the Broadcast Source Information stored in remote Scan delegators + * + * @return returns the List of Broadcast Source Information {@link #BleBroadcastSourceInfo} stored in + * remote and its corresponding state or null in case if there are nothing + * + * @hide + */ + public List getAllBroadcastSourceInformation () { + if (mBluetoothSyncHelper == null) { + log(TAG, "GetNumberOfAcceptableBroadcastSources: no BluetoothSyncHelper handle"); + return null; + } + return mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice); + } + + private static void log(String TAG, String msg) { + BleBroadcastSourceInfo.BASS_Debug(TAG, msg); + } +} diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f3d61d851979bfca0855fca0c360911365a9ab --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java @@ -0,0 +1,231 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package android.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.annotation.IntDef; +import android.compat.annotation.UnsupportedAppUsage; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import java.util.Objects; +import android.util.Log; +import java.util.List; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * This class provides Interface to select the Broadcast source channels + * to be synchronized from the Broadcast source. these Broadcast source channels + * are mapped to the BIS indicies that given Broadcast source is broadcasting with. + * + *

This also acts as general data structure for updating the Broadcast + * source channel information + * + * This class is used to input the User provided data for below operations + * {@link BleBroadcastAudioScanAssistManager#addBroadcastSource}, + * {@link BleBroadcastAudioScanAssistManager#updateBroadcastSource} and + * + * mIndex : index is the Identifier for Broadcast channel + * mDescription: Description describing the type of Broadcast data being broadcasted + * mStatus: TRUE means broadcast source channel need to be synchronized + * FALSE means broadcast source channel need NOT be synchronized + * + * @hide + */ +public final class BleBroadcastSourceChannel implements Parcelable { + + private static final String TAG = "BleBroadcastSourceChannel"; + private int mIndex; + private String mDescription; + private boolean mStatus; + private int mSubGroupId; + private byte[] mMetadata; + + public BleBroadcastSourceChannel (int index, + String description, + boolean st, + int aSubGroupId, + byte[] aMetadata) { + mIndex = index; + mDescription = description; + mStatus = st; + mSubGroupId = aSubGroupId; + if (aMetadata != null && aMetadata.length != 0) { + mMetadata = new byte[aMetadata.length]; + System.arraycopy(aMetadata, 0, mMetadata, 0, aMetadata.length); + } + } + @Override + public boolean equals(Object o) { + if (o instanceof BleBroadcastSourceChannel) { + BleBroadcastSourceChannel other = (BleBroadcastSourceChannel) o; + return (other.mIndex == mIndex + && other.mDescription == mDescription + && other.mStatus == mStatus + ); + } + return false; + } + @Override + public int hashCode() { + return Objects.hash(mIndex, mDescription, mStatus); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return mDescription; + } + + /** + * Gets the Source Id of the BleBroadcastSourceChannel Object + * + * @return byte representing the Source Id of the Broadcast Source Info Object + * {@link #BROADCAST_ASSIST_INVALID_SOURCE_ID} in case if this field is not valid + * @hide + */ + public int getIndex () { + return mIndex; + } + + /** + * Gets the Broadcast source Device object from the BleBroadcastSourceChannel Object + * + * @return BluetoothDevice object for Broadcast source device + * @hide + */ + public String getDescription () { + return mDescription; + } + + /** + * Gets the status of given BleBroadcastSourceChannel + * + * @return true if selected, false otherwise + * @hide + * + * @deprecated + */ + + public boolean getStatus () { + return mStatus; + } + + /** + * Gets the address type of the Broadcast source advertisement for the BleBroadcastSourceChannel Object + * + * @return byte addressType, this can be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} OR {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} + * @hide + * + * @deprecated + */ + + public byte[] getMetadata () { + return mMetadata; + } + + /** + * Gets the subgroup Id that broadcast Channel belongs + * Internal helper function + * + * @hide + * @deprecated + */ + public int getSubGroupId () { + return mSubGroupId; + } + + /** + * Sets the status of given BleBroadcastSourceChannel + * + * @return true if selected, false otherwise + * @hide + * + * @deprecated + */ + public void setStatus (boolean status) { + mStatus = status; + } + + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BleBroadcastSourceChannel createFromParcel(Parcel in) { + + log(TAG, "createFromParcel>"); + final int index = in.readInt(); + final String desc = in.readString(); + final boolean status = in.readBoolean(); + final int subGroupId = in.readInt(); + + final int metadataLength = in.readInt(); + byte[] metadata = null; + if (metadataLength > 0) { + metadata = new byte[metadataLength]; + in.readByteArray(metadata); + } + + BleBroadcastSourceChannel srcChannel = + new BleBroadcastSourceChannel(index, desc, status, subGroupId, metadata); + log(TAG, "createFromParcel:" + srcChannel); + return srcChannel; + } + + public BleBroadcastSourceChannel[] newArray(int size) { + return new BleBroadcastSourceChannel[size]; + } + }; + + + + @Override + public void writeToParcel(Parcel out, int flags) { + log(TAG, "writeToParcel>"); + out.writeInt(mIndex); + out.writeString(mDescription); + out.writeBoolean(mStatus); + out.writeInt(mSubGroupId); + if (mMetadata != null) { + out.writeInt(mMetadata.length); + out.writeByteArray(mMetadata); + } else { + out.writeInt(0); + } + log(TAG, "writeToParcel:" + toString()); + } + private static void log(String TAG, String msg) { + BleBroadcastSourceInfo.BASS_Debug(TAG, msg); + } +}; + diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..1e159017c9a8d2d12190ccd6b53bf1e637b38fc0 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java @@ -0,0 +1,1052 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package android.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.annotation.IntDef; +import android.compat.annotation.UnsupportedAppUsage; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import java.util.Objects; +import android.util.Log; +import java.util.List; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.HashMap; + +/** + * This class provides methods to get various information of Broadcast + * source information stored in remote. Users can call get/set methods + * enquire the required information + * + *

This also acts as general data structure for updating the Broadcast + * source information + * This class is used to input the User provided data for below operations + * {@link BleBroadcastAudioScanAssistManager#addBroadcastSource}, + * {@link BleBroadcastAudioScanAssistManager#updateBroadcastSource} and + * {@link BleBroadcastAudioScanAssistManager#setBroadcastCode} + * + *

This is also used to pack all Broadcast source information as part of {@link #ACTION_BROADCAST_RECEIVER_STATE} + * Intent. User can retrive the {@link BleBroadcastSourceInfo} using {@link BleBroadcastSourceInfo#EXTRA_RECEIVER_STATE} + * extra field + * @hide + */ +public final class BleBroadcastSourceInfo implements Parcelable { + + private static final String TAG = "BleBroadcastSourceInfo"; + private static final boolean BASS_DBG = Log.isLoggable(TAG, Log.VERBOSE); + + /** @hide + * @deprecated + */ + @Deprecated + @IntDef(prefix = "BROADCAST_ASSIST_ADDRESS_TYPE_", value = { + BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC, + BROADCAST_ASSIST_ADDRESS_TYPE_RANDOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistAddressType {} + + /** + * Address Type of the LE Broadcast Audio Source Device + * Specifies whether LE Broadcast Audio Source device using public OR + * random address for the LE Audio broadcasts + * + * @deprecated + */ + @Deprecated + public static final int BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC = 0; + /** + * Address Type of the LE Broadcast Audio Source Device + * Specifies whether LE Broadcast Audio Source device using public OR + * random address for the LE Audio broadcasts + * + * @deprecated + */ + @Deprecated + public static final int BROADCAST_ASSIST_ADDRESS_TYPE_RANDOM = 1; + /** + * Address Type of the LE Broadcast Audio Source Device + * Specifies whether LE Broadcast Audio Source device using public PR + * random address for the LE Audio broadcasts + * + * @deprecated + */ + @Deprecated + public static final int BROADCAST_ASSIST_ADDRESS_TYPE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef(prefix = "BROADCAST_ASSIST_PA_SYNC_STATE_", value = { + BROADCAST_ASSIST_PA_SYNC_STATE_IDLE, + BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ, + BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC, + BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL, + BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistMetadataSyncState {} + + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State IDLE specifies that broadcast + * receiver is not able to sync the Metada/PA + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_IDLE = 0; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State SYNCINFO REQ specifies that broadcast + * receiver requesting for SYNCINFO from the Scan Offloader to synchronie + * to Metadata/PA + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ = 1; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State INSYNC specifies that broadcast + * receiver in sync with to Metadata/PA. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC = 2; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State INSYNC specifies that broadcast + * receiver is failed to sync with Metadata/PA. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL = 3; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State SYNC NOPAST denotes that broadcast + * receiver needs PAST procedure to sync with Metadata. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST = 4; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State SYNC NOPAST denotes that broadcast + * receiver needs PAST procedure to sync with Metadata. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef(prefix = "BROADCAST_ASSIST_AUDIO_SYNC_STATE_", value = { + BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, + BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistAudioSyncState {} + + /** + * Broadcast Audio stream Sync State + * Broadcast receiver sync state w.r.t Broadcast Audio stream BIS. denotes + * receiver is not synchronized to LE Audio BIS + */ + public static final int BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; + /** + * Broadcast Audio stream Sync State + * Broadcast receiver sync state w.r.t Broadcast Audio stream BIS. denotes + * receiver is not synchronized to LE Audio BIS + */ + public static final int BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; + /** + * Broadcast Audio stream Sync State + * Broadcast receiver sync state w.r.t Broadcast Audio stream BIS. denotes + * receiver is not synchronized to LE Audio BIS + */ + public static final int BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID = 0xFFFF; + + + /** @hide */ + @IntDef(prefix = "BROADCAST_ASSIST_ENC_STATE_", value = { + BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED, + BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED, + BROADCAST_ASSIST_ENC_STATE_DECRYPTING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistEncryptionState {} + /** + * Encryption Status at the LE Audio broadcast receiver side + * UNENCRYPTED denoted that broadcast receiver is in sync with an uncrypted + * broadcasted audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED = 0; + /** + * Encryption Status at the LE Audio broadcast receiver side + * PIN_NEEDED denote that the Broadcast receiver needs broadcast PIN + * to sync and listen to Broadcasted Audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED = 1; + /** + * Encryption Status at the LE Audio broadcast receiver side + * state DECRYPTING denote that the Broadcast receiver is able to decrypt + * and listen to the Broadcasted Audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_DECRYPTING = 2; + /** + * Encryption Status at the LE Audio broadcast receiver side + * state BADCODE denote that the Broadcast receiver has got bad code + * and not able decrypt + * Incorrect code that Scan delegator tried to decrypt can be retrieved from + * + */ + public static final int BROADCAST_ASSIST_ENC_STATE_BADCODE = 3; + /** + * Encryption Status at the LE Audio broadcast receiver side + * state DECRYPTING denote that the Broadcast receiver is able to decrypt + * and listen to the Broadcasted Audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_INVALID = 0xFFFF; + + /* + * Invalid Broadcast source Information Id + */ + public static final byte BROADCAST_ASSIST_INVALID_SOURCE_ID = (byte)0x00; + /* + * Invalid Broadcaster Identifier of the given Broadcast Source + */ + public static final int BROADCASTER_ID_INVALID = 0xFFFF; + /** + * Used as an int extra field in {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} + * intent notifys the Broadcast Source Information to Application layer + * + *

Source Info object can be extracted using this extra field at Application layer + * + * This is used to read the {@link BleBroadcastSourceInfo } parcelable object + * @hide + */ + public static final String EXTRA_SOURCE_INFO = "android.bluetooth.device.extra.SOURCE_INFO"; + /** + * Used as an int extra field in {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} + * intent Broadcast Source Information to Application layer + * + *

Index of the Source Info object can be extracted using this extra field at Application layer + * + * This is used to read the {@link BleBroadcastSourceInfo } parcelable object + * @hide + */ + public static final String EXTRA_SOURCE_INFO_INDEX = "android.bluetooth.device.extra.SOURCE_INFO_INDEX"; + /** + * Used as an int extra field in {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} + * intent notifys the Broadcast Source Information to Application layer + * + *

Maximm number of the Broadcast Source Information that given broadcast receiver can hold, can be extracted using + * this extra field at Application layer + * + * @hide + */ + public static final String EXTRA_MAX_NUM_SOURCE_INFOS = "android.bluetooth.device.extra.MAX_NUM_SOURCE_INFOS"; + private byte mSourceId; + private @BroadcastAssistAddressType int mSourceAddressType; + private BluetoothDevice mSourceDevice; + private byte mSourceAdvSid; + private int mBroadcasterId; + private @BroadcastAssistMetadataSyncState int mMetaDataSyncState; + private @BroadcastAssistAudioSyncState int mAudioSyncState; + private Map mAudioBisIndexList = new HashMap (); + private @BroadcastAssistEncryptionState int mEncyptionStatus; + private Map mMetadataList = new HashMap(); + private String mBroadcastCode; + private byte[] mBadBroadcastCode; + private byte mNumSubGroups; + private static final int BIS_NO_PREF = 0xFFFFFFFF; + private static final int BROADCAST_CODE_SIZE = 16; + + /** + * Constructor to create an Empty object of {@link BleBroadcastSourceInfo } with given source Id, + * which contains, Broadcast reciever state information for Broadcast Assistant Usecases. + * + * This is mainly used to represent the Empty Broadcast source entries + * + * @param sourceId Source Id for this broadcast source info object + * + * @deprecated + * @hide + */ + @Deprecated + public BleBroadcastSourceInfo (byte sourceId) { + mSourceId = sourceId; + mMetaDataSyncState = BROADCAST_ASSIST_PA_SYNC_STATE_INVALID; + mAudioSyncState = BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID; + mSourceAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_INVALID; + mSourceDevice = null; + mSourceAdvSid = (byte)0x00; + mEncyptionStatus = BROADCAST_ASSIST_ENC_STATE_INVALID; + mBroadcastCode = null; + mBadBroadcastCode = null; + mNumSubGroups = 0; + mBroadcasterId = BROADCASTER_ID_INVALID; + } + /** + * Constructor to create an object of {@link BleBroadcastSourceInfo } which contains + * Broadcast reciever state information for Broadcast Assistant Usecases. + * This is mainly used for input purpose of {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} + * operation + * + * @param audioSource BluetoothDevice object whcih is selected as Broadcast source + * @param advSid advertising Sid of the Broadcast source device for which reciever synchronized with. + * @param addressType type of address. This can be be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} or + * {@link #BROADCAST_ASSIST_ADDRESS_TYPE_RANDOM} + * @param metadataSyncState sync status of metadata at the receiver side from this Broadcast source. This can + * be one of {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * @param audioSyncState Audio sync status of metadata at the receiver side from this broadcast source. This can be + * one of {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} + * @param audioBisIndex Audio BIS index for what Broadcast reciever synchronized with + * @param metadataLength Length of the metadata field + * @prama metadata metadata information about the type Broadcast information being synchronized at receiver side + * + * + * @hide + */ + /*package*/ BleBroadcastSourceInfo (BluetoothDevice audioSource, + byte advSid, + @BroadcastAssistAddressType int addressType, + @BroadcastAssistMetadataSyncState int metadataSyncstate, + @BroadcastAssistAudioSyncState int audioSyncstate, + List selectedBISIndicies + ) { + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mSourceAddressType = addressType; + mSourceDevice = audioSource; + mSourceAdvSid = advSid; + mBroadcasterId = BROADCASTER_ID_INVALID; + if (selectedBISIndicies == null) { + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + } else { + for (int i=0; i> selectedBISIndiciesList, + Map metadataList + ) { + mSourceId = sourceId; + mSourceAddressType = addressType; + mSourceDevice = audioSource; + mSourceAdvSid = advSid; + mBroadcasterId = broadcasterId; + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mEncyptionStatus = encryptionStatus; + if (badCode != null) { + mBadBroadcastCode = new byte[BROADCAST_CODE_SIZE]; + System.arraycopy(badCode, 0, mBadBroadcastCode, 0, mBadBroadcastCode.length); + } + mNumSubGroups = numSubGroups; + int audioBisIndex = 0; + if (selectedBISIndiciesList != null) { + for (Map.Entry> entry : selectedBISIndiciesList.entrySet()) { + List selectedBISIndicies = entry.getValue(); + if (selectedBISIndicies == null) { + //do nothing + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + } else { + for (int i=0; i entry : metadataList.entrySet()) { + byte[] metadata = entry.getValue(); + if (metadata != null && metadata.length != 0) { + byte[] mD = new byte[metadata.length]; + System.arraycopy(metadata, 0, mD, 0, metadata.length); + } + mMetadataList.put(entry.getKey(), metadata); + } + } + } + + /** + * Constructor override to create an object of {@link BleBroadcastSourceInfo } which contains + * Broadcast reciever state information for Broadcast Assistant Usecases. + * + * This is mainly used for output purpose to create an object from the receiver state information + * read from the remote BASS server. This will be packed and broadcasted as an Intent using + * {@link #ACTION_BROADCAST_RECEIVER_STATE} + * + * @param audioSource BluetoothDevice object whcih is selected as Broadcast source + * @param advSid advertising Sid of the Broadcast source device for which reciever synchronized with + * @param addressType type of address. This can be be one of {@link #BLE_ASSIST_ADDRESS_TYPE_PUBLIC} or + * {@link #BLE_ASSIST_ADDRESS_TYPE_RANDOM} + * @param metadataSyncState sync status of metadata at the receiver side from this Broadcast source. This can + * be one of {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * @param audioSyncState Audio sync status of metadata at the receiver side from this broadcast source. This can be + * one of {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} + * @param encryptionStatus Encryotion state at Broadcast receiver. This can be one of {@link #BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED}, + * {@link #BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED} OR {@link #BROADCAST_ASSIST_ENC_STATE_DECRYPTING} + * @param audioBisIndex Audio BIS index for what Broadcast reciever synchronized with + * @param metadataLength Length of the metadata field + * @prama metadata metadata information about the type Broadcast information being synchronized at receiver side + * @param broadcastCode Numeric Character String maximum of 16 characters in length, which serves as broadcast PIN code + * + * @hide + */ + /*package*/ BleBroadcastSourceInfo (BluetoothDevice device, + byte sourceId, + byte advSid, + @BroadcastAssistAddressType int addressType, + @BroadcastAssistMetadataSyncState int metadataSyncstate, + @BroadcastAssistAudioSyncState int audioSyncstate, + List selectedBISIndicies, + @BroadcastAssistEncryptionState int encryptionStatus, + String broadcastCode) { + mSourceId = sourceId; + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mEncyptionStatus = encryptionStatus; + mSourceAddressType = addressType; + mSourceDevice = device; + mSourceAdvSid = advSid; + mBroadcasterId = BROADCASTER_ID_INVALID; + if (selectedBISIndicies == null) { + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + } else { + for (int i=0; i bisIndiciesList, + Map metadataList + ) { + mSourceId = sourceId; + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mEncyptionStatus = encryptionStatus; + mSourceAddressType = addressType; + mSourceDevice = device; + mSourceAdvSid = advSid; + mBroadcasterId = broadcasterId; + mBroadcastCode = broadcastCode; + if (badCode != null && badCode.length != 0) { + mBadBroadcastCode= new byte[badCode.length]; + System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); + } + mNumSubGroups = numSubGroups; + mAudioBisIndexList = new HashMap (bisIndiciesList); + mMetadataList = new HashMap (metadataList); + } + + @Override + public boolean equals(Object o) { + if (o instanceof BleBroadcastSourceInfo) { + BleBroadcastSourceInfo other = (BleBroadcastSourceInfo) o; + BASS_Debug(TAG, "other>> " + o.toString()); + BASS_Debug(TAG, "local>> " + toString()); + return (other.mSourceId == mSourceId + && other.mMetaDataSyncState == mMetaDataSyncState + && other.mAudioSyncState == mAudioSyncState + && other.mSourceAddressType == mSourceAddressType + && other.mSourceDevice == mSourceDevice + && other.mSourceAdvSid == mSourceAdvSid + && other.mEncyptionStatus == mEncyptionStatus + && other.mBroadcastCode == mBroadcastCode + && other.mBroadcasterId == mBroadcasterId + ); + } + return false; + } + + public boolean isEmptyEntry() { + boolean ret = false; + if (mMetaDataSyncState == (int)BROADCAST_ASSIST_PA_SYNC_STATE_INVALID && + mAudioSyncState == (int)BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID && + mSourceAddressType == (int)BROADCAST_ASSIST_ADDRESS_TYPE_INVALID && + mSourceDevice == null && + mSourceAdvSid == (byte)0 && + mEncyptionStatus == (int)BROADCAST_ASSIST_ENC_STATE_INVALID + ) { + ret = true; + } + BASS_Debug(TAG, "isEmptyEntry returns: " + ret); + return ret; + } + + public boolean matches(BleBroadcastSourceInfo srcInfo) { + boolean ret = false; + if (srcInfo == null) { + ret = false; + } else { + if (mSourceDevice == null) { + if (mSourceAdvSid == srcInfo.getAdvertisingSid() && + mSourceAddressType == srcInfo.getAdvAddressType()) { + ret = true; + } + } else { + if (mSourceDevice.equals(srcInfo.getSourceDevice()) && + mSourceAdvSid == srcInfo.getAdvertisingSid() && + mSourceAddressType == srcInfo.getAdvAddressType() && + mBroadcasterId == srcInfo.getBroadcasterId()) { + ret = true; + } + } + } + BASS_Debug(TAG, "matches returns: " + ret); + return ret; + } + + @Override + public int hashCode() { + return Objects.hash(mSourceId, mMetaDataSyncState, mAudioSyncState, + mSourceAddressType, mSourceDevice, mSourceAdvSid, + mEncyptionStatus, mBroadcastCode); + } + + @Override + public int describeContents() { + return 0; + } + @Override + public String toString() { + return "{BleBroadcastSourceInfo : mSourceId" + mSourceId + + " sourceDevice: " + mSourceDevice + + " addressType: " + mSourceAddressType + + " mSourceAdvSid:" + mSourceAdvSid + + " mMetaDataSyncState:" + mMetaDataSyncState + + " mAudioSyncState" + mAudioSyncState + + " mEncyptionStatus" + mEncyptionStatus + + " mBadBroadcastCode" + mBadBroadcastCode + + " mNumSubGroups" + mNumSubGroups + + " mBroadcastCode" + mBroadcastCode + + " mAudioBisIndexList" + mAudioBisIndexList + + " mMetadataList" + mMetadataList + + " mBroadcasterId" + mBroadcasterId + + "}"; + } + + /** + * Gets the Source Id of the BleBroadcastSourceInfo Object + * + * @return byte representing the Source Id of the Broadcast Source Info Object + * {@link #BROADCAST_ASSIST_INVALID_SOURCE_ID} in case if this field is not valid + * @hide + */ + public byte getSourceId () { + return mSourceId; + } + + /** + * Sets the Source Id of the BleBroadcastSourceInfo Object + * + * @param byte source Id for the BleBroadcastSourceInfo Object + * + * @hide + */ + public void setSourceId (byte sourceId) { + mSourceId = sourceId; + } + + /** + * Sets the Broadcast source device for the BleBroadcastSourceInfo Object + * + * @param BluetoothDevice which need to be set as Broadcast source device + * @hide + */ + public void setSourceDevice(BluetoothDevice sourceDevice) { + mSourceDevice = sourceDevice; + } + + /** + * Gets the Broadcast source Device object from the BleBroadcastSourceInfo Object + * + * @return BluetoothDevice object for Broadcast source device + * @hide + */ + public BluetoothDevice getSourceDevice () { + return mSourceDevice; + } + + /** + * Sets the address type of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @param byte addressType, this can be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} OR {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} + * @hide + */ + public void setAdvAddressType(int addressType) { + mSourceAddressType = addressType; + } + + /** + * Gets the address type of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @return byte addressType, this can be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} OR {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} + * @hide + * + * @deprecated + */ + @UnsupportedAppUsage + @Deprecated + public int getAdvAddressType () { + return mSourceAddressType; + } + + /** + * Sets the advertising Sid of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @param byte advertising Sid value + * @hide + */ + public void setAdvertisingSid(byte advSid) { + mSourceAdvSid = advSid; + } + + /** + * Gets the advertising Sid of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @return byte advertising Sid value + * @hide + */ + public byte getAdvertisingSid () { + return mSourceAdvSid; + } + + /** + * Gets the Broadcast Id of the Broadcast source of the BleBroadcastSourceInfo Object + * + * @return int broadcast source Identifier + * @hide + */ + public int getBroadcasterId () { + return mBroadcasterId; + } + + /** + * Sets the Metadata sync status at the Broadcast receiver side for the BleBroadcastSourceInfo Object + * + * @param BroadcastAssistMetadataSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * + * @hide + */ + /*package*/ void setMetadataSyncState(@BroadcastAssistMetadataSyncState int metadataSyncState) { + mMetaDataSyncState = metadataSyncState; + } + + /** + * Gets the Metadata sync status at the Broadcast receiver side from the BleBroadcastSourceInfo Object + * + * @return BroadcastAssistMetadataSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * + * @hide + */ + public @BroadcastAssistMetadataSyncState int getMetadataSyncState () { + return mMetaDataSyncState; + } + + /** + * Sets the Audio sync status at the Broadcast receiver side for the BleBroadcastSourceInfo Object + * + * @param BroadcastAssistAudioSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} + * + * @hide + */ + /*package*/ void setAudioSyncState(@BroadcastAssistAudioSyncState int audioSyncState) { + mAudioSyncState = audioSyncState; + } + + /** + * Gets the Audio sync status at the Broadcast receiver side from the BleBroadcastSourceInfo Object + * + * @return BroadcastAssistAudioSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} * + * @hide + */ + public @BroadcastAssistAudioSyncState int getAudioSyncState () { + return mAudioSyncState; + } + + /** + * Sets the Encryption status at the Broadcast receiver side for the BleBroadcastSourceInfo Object + * + * @param BroadcastAssistEncryptionState representing the state of Meta data sync status. This can be one of + * {@link #BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED}, + * {@link #BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED}, {@link #BROADCAST_ASSIST_ENC_STATE_DECRYPTING} + * Or {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * @hide + */ + /*package*/ void setEncryptionStatus(@BroadcastAssistEncryptionState int encryptionStatus) { + mEncyptionStatus = encryptionStatus; + } + + /** + * Gets the Audio sync status at the Broadcast receiver side from the BleBroadcastSourceInfo Object + * + * @return BroadcastAssistEncryptionState representing the state of Meta data sync status. This can be one of + * {@link #BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED}, + * {@link #BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED} ,{@link #BROADCAST_ASSIST_ENC_STATE_DECRYPTING} + * Or {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * @hide + */ + public @BroadcastAssistEncryptionState int getEncryptionStatus () { + return mEncyptionStatus; + } + + /** + * Gets the Incorrect Broadcast code with which Scan delegator try + * decrypt the Broadcast audio and failed + * + * This code is valid only if {@link #getEncryptionStatus} returns + * {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * + * @param byte[] byte array containing bad broadcast value + * null if the current Encryptetion status is + * not {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * + * @hide + */ + public byte[] getBadBroadcastCode () { + return mBadBroadcastCode; + } + + /** + * Gets the number of subgroups of the BleBroadcastSourceInfo Object + * + * @return byte number of subgroups + * @hide + * + * @deprecated + */ + @UnsupportedAppUsage + @Deprecated + public byte getNumberOfSubGroups () { + return mNumSubGroups; + } + + /** + * Sets the Audio Broadcast channels to which receiver need to be synchronized with, + * for BleBroadcastSourceInfo Object + * + * + * @param int audioBis Index to which reciever need to be synchronized with + * @hide + */ + /*package*/ void setBroadcastChannelsSyncStatus(List selectedBISIndicies) { + if (selectedBISIndicies == null) { + //set No preference + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + return; + } + for (int i=0; i getBroadcastChannelsSyncStatus () { + List bcastIndicies = new ArrayList(); + for (int i=0; i>1; + index++; + } + } + + BASS_Debug(TAG, "returning Bisindicies:" + bcastIndicies); + return bcastIndicies; + } + + @UnsupportedAppUsage + @Deprecated + public Map getBisIndexList() { + return mAudioBisIndexList; + } + + /*package*/ void setBroadcastCode(String broadcastCode) { + mBroadcastCode = broadcastCode; + } + + @UnsupportedAppUsage + @Deprecated + public void setBroadcasterId(int broadcasterId) { + mBroadcasterId = broadcasterId; + } + + /** + * Gets the broadcastCode value from BleBroadcastSourceInfo Object + * + * @param String broadcast code from the BleBroadcastSourceInfo object + * @hide + * + * @deprecated + */ + @UnsupportedAppUsage + @Deprecated + public String getBroadcastCode () { + return mBroadcastCode; + } + + private void writeMapToParcel(Parcel dest, Map bisIndexList) { + dest.writeInt(bisIndexList.size()); + for (Map.Entry entry : bisIndexList.entrySet()) { + dest.writeInt(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } + + private static void readMapFromParcel(Parcel in, Map bisIndexList) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer value = in.readInt(); + bisIndexList.put(key, value); + } + } + + private void writeMetadataListToParcel(Parcel dest, Map metadataList) { + dest.writeInt(metadataList.size()); + for (Map.Entry entry : metadataList.entrySet()) { + dest.writeInt(entry.getKey()); + byte[] metadata = entry.getValue(); + if (metadata != null) { + dest.writeInt(metadata.length); + dest.writeByteArray(metadata); + } + } + } + + private static void readMetadataListFromParcel(Parcel in, Map metadataList) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer metaDataLen = in.readInt(); + byte[] metadata = null; + if (metaDataLen != 0) { + metadata = new byte[metaDataLen]; + in.readByteArray(metadata); + } + metadataList.put(key, metadata); + } + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BleBroadcastSourceInfo createFromParcel(Parcel in) { + + BASS_Debug(TAG, "createFromParcel>"); + final byte sourceId = in.readByte(); + final int sourceAddressType = in.readInt(); + final BluetoothDevice sourceDevice = in.readTypedObject( + BluetoothDevice.CREATOR); + final byte sourceAdvSid = in.readByte(); + final int broadcastId = in.readInt(); + BASS_Debug(TAG, "broadcastId" + broadcastId); + final int metaDataSyncState = in.readInt(); + final int audioSyncState = in.readInt(); + BASS_Debug(TAG, "audioSyncState" + audioSyncState); + final int encyptionStatus = in.readInt(); + final int badBroadcastLen = in.readInt(); + byte[] badBroadcastCode = null; + if (badBroadcastLen > 0) { + badBroadcastCode = new byte[badBroadcastLen]; + in.readByteArray(badBroadcastCode); + } + final byte numSubGroups = in.readByte(); + final String broadcastCode = in.readString(); + Map bisIndexList = new HashMap (); + readMapFromParcel(in, bisIndexList); + Map metadataList = new HashMap (); + readMetadataListFromParcel(in, metadataList); + + BleBroadcastSourceInfo srcInfo = new BleBroadcastSourceInfo(sourceDevice, sourceId, sourceAdvSid, broadcastId, + sourceAddressType, metaDataSyncState,audioSyncState, + encyptionStatus, broadcastCode, badBroadcastCode, numSubGroups, bisIndexList, metadataList); + BASS_Debug(TAG, "createFromParcel:" + srcInfo); + return srcInfo; + } + + public BleBroadcastSourceInfo[] newArray(int size) { + return new BleBroadcastSourceInfo[size]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + BASS_Debug(TAG, "writeToParcel>"); + out.writeByte(mSourceId); + out.writeInt(mSourceAddressType); + out.writeTypedObject(mSourceDevice, 0); + out.writeByte(mSourceAdvSid); + out.writeInt(mBroadcasterId); + out.writeInt(mMetaDataSyncState); + out.writeInt(mAudioSyncState); + out.writeInt(mEncyptionStatus); + if (mBadBroadcastCode != null) { + out.writeInt(mBadBroadcastCode.length); + out.writeByteArray(mBadBroadcastCode); + } else { + //write ZERO to parcel to say no badBroadcastcode + out.writeInt(0); + } + out.writeByte(mNumSubGroups); + out.writeString(mBroadcastCode); + writeMapToParcel(out, mAudioBisIndexList); + writeMetadataListToParcel(out, mMetadataList); + BASS_Debug(TAG, "writeToParcel:" + toString()); + } + + static void BASS_Debug(String TAG, String msg) { + if (BASS_DBG) { + Log.d(TAG, msg); + } + } + +}; + diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java new file mode 100644 index 0000000000000000000000000000000000000000..b709078cb7eaeafddbcbd5f7400914a263f1c789 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java @@ -0,0 +1,280 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package android.bluetooth; + +import android.Manifest; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; +import android.app.ActivityThread; +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the public APIs to control the Bluetooth Broadcast + * profile. + * + *

BluetoothBroadcast is a proxy object for controlling the Bluetooth + * Broadcast Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} + * to get the BluetoothBroadcast proxy object. + * + * @hide + */ + +public final class BluetoothBroadcast implements BluetoothProfile{ + private static final String TAG = "BluetoothBroadcast"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Intent used to broadcast the change in broadcast state. + * + *

This intent will have 3 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current state of the profile.
  • + *
  • {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
  • + *
+ * + *

{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_Disabled}, {@link #Enabling}, + * {@link #STATE_ENABLED}, {@link #STATE_DISABLING}. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_STATE_CHANGED = + "android.bluetooth.broadcast.profile.action.BROADCAST_STATE_CHANGED"; + + /** + * Intent used to broadcast the change in broadcast audio state. + * + *

This intent will have 3 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current audio state .
  • + *
  • {@link #EXTRA_PREVIOUS_STATE}- The previous audio state.
  • + *
+ * + *

{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_AUDIO_STATE_CHANGED = + "android.bluetooth.broadcast.profile.action.BROADCAST_AUDIO_STATE_CHANGED"; + + /** + * Intent used to broadcast encryption key generation status. + * + *

This intent will have 2 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current audio state .
  • + *
+ * + *

{@link #EXTRA_STATE} can be any of + * {@link #TRUE}, {@link #FALSE}, + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_ENCRYPTION_KEY_GENERATED = + "android.bluetooth.broadcast.profile.action.BROADCAST_ENCRYPTION_KEY_GENERATED"; + + public static final int STATE_DISABLED = 10; + public static final int STATE_ENABLING = 11; + public static final int STATE_ENABLED = 12; + public static final int STATE_DISABLING = 13; + public static final int STATE_STREAMING = 14; + public static final int STATE_PLAYING = 10; + public static final int STATE_NOT_PLAYING = 11; + + private BluetoothAdapter mAdapter; + private final BluetoothProfileConnector mProfileConnector = + new BluetoothProfileConnector(this, BluetoothProfile.BROADCAST, "BluetoothBroadcast", + IBluetoothBroadcast.class.getName()) { + @Override + public IBluetoothBroadcast getServiceInterface(IBinder service) { + return IBluetoothBroadcast.Stub.asInterface(Binder.allowBlocking(service)); + } + }; + /** + * Create a BluetoothBroadcast proxy object for interacting with the local + * Bluetooth Broadcast service. + * @hide + */ + /*package*/ BluetoothBroadcast(Context context, ServiceListener listener) { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mProfileConnector.connect(context, listener); + } + + /* + * @hide + */ + //@UnsupportedAppUsage + /*package*/ void close() { + mProfileConnector.disconnect(); + } + + /* + * @hide + */ + private IBluetoothBroadcast getService() { + return mProfileConnector.getService(); + } + + @Override + public void finalize() { + // The empty finalize needs to be kept or the + // cts signature tests would fail. + } + /* + * @hide + */ + //@UnsupportedAppUsage + public boolean SetBroadcastMode(boolean enable) { + if (DBG) log("EnableBroadcast"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.SetBroadcast(enable, packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + /** + * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * + * @throws UnsupportedOperationException + */ + @Override + public int getConnectionState(BluetoothDevice device) { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + /** + * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * + * @throws UnsupportedOperationException + */ + @Override + public List getDevicesMatchingConnectionStates(int[] states) { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + /** + * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * + * @throws UnsupportedOperationException + */ + @Override + public List getConnectedDevices() { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + + /* + * @hide + */ + public boolean SetEncryption(boolean enable, int enc_len/*4bytes,16bytes*/, boolean use_existing) { + if (DBG) log("SetEncryption"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.SetEncryption(enable, enc_len, use_existing, packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + /* + * @hide + */ + public byte[] GetEncryptionKey() { + if (DBG) log("GetBroadcastEncryptionKey"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.GetEncryptionKey(packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return null; + } + } + /* + * @hide + */ + public int GetBroadcastStatus() { + if (DBG) log("GetBroadcastStatus"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.GetBroadcastStatus(packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return STATE_DISABLED; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return STATE_DISABLED; + } + } + //@UnsupportedAppUsage +// public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {return null;} + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} + + diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..7072891d9745fc6bef8647b5ef97d85b67f7b5d5 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java @@ -0,0 +1,736 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package android.bluetooth; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.IBluetoothSyncHelper; +import android.bluetooth.BluetoothAdapter.LeScanCallback; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.content.Context; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; +import android.bluetooth.le.BluetoothLeScanner; +import android.os.SystemProperties; +import java.util.IdentityHashMap; + +/** + * This class provides methods to perform Broadcast Scan Assistance client Profile related + * operations. + * It uses Bluetooth GATT APIs to achieve Braodcast Scan assistance client operations. Application should ensure + * BASS profile is connected with the given remote device before performing the operations using + * {@link BleBroadcastAudioScanAssistManager} interface operations + * + *

BluetoothSyncHelper is a proxy object for controlling the Bluetooth Scan Offloader (BASS client) + * Service via IPC. + * + *

Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothScanOfflaoder proxy object. Use + * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. + * + *

Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothSyncHelper proxy object. Use + * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. + * + * Note: Most of the methods here require + * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @hide + */ +public final class BluetoothSyncHelper implements BluetoothProfile { + + private static final String TAG = "BluetoothSyncHelper"; + private static final boolean DBG = true; + + private BluetoothAdapter mBluetoothAdapter; + /* maps callback, to callback wrapper and sync handle */ + private Map mAppCallbackWrappers; + + private Map sBleAssistManagers = null; + private Context mContext = null; + + /** + * Intent used to broadcast the change in connection state of the Bass client + * profile. + * + *

This intent will have 3 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current state of the profile.
  • + *
  • {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
  • + *
  • {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
  • + *
+ * + *

{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.bc.profile.action.CONNECTION_STATE_CHANGED"; + + + private final BluetoothProfileConnector mProfileConnector = + new BluetoothProfileConnector(this, BluetoothProfile.BC_PROFILE, + "BluetoothSyncHelper", IBluetoothSyncHelper.class.getName()) { + @Override + public IBluetoothSyncHelper getServiceInterface(IBinder service) { + return IBluetoothSyncHelper.Stub.asInterface(Binder.allowBlocking(service)); + } + }; + + /*package*/ void close() { + mProfileConnector.disconnect(); + mAppCallbackWrappers.clear(); + } + + /*package*/ IBluetoothSyncHelper getService() { + return mProfileConnector.getService(); + } + + static boolean isSupported() { + boolean isSupported = SystemProperties.getBoolean("persist.vendor.service.bt.bc", true); + log("BluetoothSyncHelper: isSupported returns " + isSupported); + return isSupported; + } + /** + * Create a BluetoothHeadset proxy object. + */ + /*package*/ BluetoothSyncHelper(Context context, ServiceListener listener) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mProfileConnector.connect(context, listener); + BluetoothManager bluetoothManager = context.getSystemService( + BluetoothManager.class); + mAppCallbackWrappers = new IdentityHashMap(); + sBleAssistManagers = new IdentityHashMap(); + mContext = context; + } + + /** + * Interface to get Broadcast Audio Scan assistance for LE Audio usecases.This is instantiated per BluetoothDevice + * which is Scan delegator + * Application will get an Instance of the {@link BleBroadcastAudioScanAssistManager} for the given + * scan delegator device + * + * @param BluetoothDevice Scan Delegator device for which BLE Broadcast SCAN Assistance operations will + * be performed + * @param {@link #BleBroadcastAudioScanAssistCallback} where callbacks related to BLE Broadcast Scan + * assistance will be deliverd + * @hide + */ + public BleBroadcastAudioScanAssistManager getBleBroadcastAudioScanAssistManager( + BluetoothDevice device, + BleBroadcastAudioScanAssistCallback callback) { + if (isSupported() == false) { + Log.e(TAG, "Broadcast scan assistance not supported"); + return null; + } + + BleBroadcastAudioScanAssistManager assistMgr = null; + if (sBleAssistManagers != null) { + assistMgr = sBleAssistManagers.get(device); + } + if (assistMgr == null) { + assistMgr = new BleBroadcastAudioScanAssistManager(this, device, + callback); + } else { + //object already exists, just registers the callback and retrun the same object + log("calling registerAppCb only"); + } + registerAppCallback(device, callback); + return assistMgr; + } + + + /** + * Initiate connection to a BASS server profile of the remote bluetooth device. + * + *

This API returns false in scenarios like the profile on the + * device is already connected or Bluetooth is not turned on. + * When this API returns true, it is guaranteed that + * connection state intent for the profile will be broadcasted with + * the state. Users can get the connection state of the profile + * from this intent. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, true otherwise + * @hide + */ + public boolean connect(BluetoothDevice device) { + log("connect(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() && isValidDevice(device)) { + return service.connect(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + /** + * Initiate disconnection from a profile + * + *

This API will return false in scenarios like the profile on the + * Bluetooth device is not in connected state etc. When this API returns, + * true, it is guaranteed that the connection state change + * intent will be broadcasted with the state. Users can get the + * disconnection state of the profile from this intent. + * + *

If the disconnection is initiated by a remote device, the state + * will transition from {@link #STATE_CONNECTED} to + * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the + * host (local) device the state will transition from + * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to + * state {@link #STATE_DISCONNECTED}. The transition to + * {@link #STATE_DISCONNECTING} can be used to distinguish between the + * two scenarios. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, true otherwise + * @hide + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() && isValidDevice(device)) { + return service.disconnect(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public @NonNull List getConnectedDevices() { + log("getConnectedDevices()"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled()) { + return service.getConnectedDevices(); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public @NonNull List getDevicesMatchingConnectionStates( + @NonNull int[] states) { + log("getDevicesMatchingStates()"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled()) { + return service.getDevicesMatchingConnectionStates(states); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public @BluetoothProfile.BtProfileState int getConnectionState( + @NonNull BluetoothDevice device) { + log("getState(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.getConnectionState(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + /** + * Get the connection policy of the profile. + * + *

The connection policy can be any of: + * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, + * {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + //@SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH) + public int getConnectionPolicy(@NonNull BluetoothDevice device) { + log("getConnectionPolicy(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.getConnectionPolicy(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } + } + + /** + * Set connection policy of the profile + * + *

The device should already be paired. + * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, + * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + //@SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + private IBleBroadcastAudioScanAssistCallback wrap(BleBroadcastAudioScanAssistCallback callback, + Handler handler) { + return new IBleBroadcastAudioScanAssistCallback.Stub() { + public void onBleBroadcastSourceFound(ScanResult scanres) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastSourceFound for " + + "scanres:" + scanres); + callback.onBleBroadcastSourceFound( + scanres); + } + }); + } + + public void onBleBroadcastAudioSourceSelected(BluetoothDevice device, int status, + List broadcastSourceChannels) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastSourceSelected for " + + "status:" + status); + callback.onBleBroadcastSourceSelected(device, + status, broadcastSourceChannels); + } + }); + } + public void onBleBroadcastAudioSourceAdded(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastAudioSourceAdded for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastAudioSourceAdded(rcvr, srcId, + status); + } + }); + } + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastAudioSourceUpdated for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastAudioSourceUpdated(rcvr, srcId, + status); + } + }); + } + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastPinUpdated for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastPinUpdated(rcvr, srcId, + status); + // App can still unregister the sync until notified it's lost. + // Remove callback after app was notifed. + //mCallbackWrappers.remove(callback); + } + }); + } + + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastAudioSourceRemoved for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastAudioSourceRemoved(rcvr, srcId, + status); + + } + }); + } + }; + } + + + boolean startScanOffload (BluetoothDevice device, boolean isGroupOp) { + log("startScanOffload(" + device + ", isGroupOp: " + isGroupOp + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.startScanOffload(device, isGroupOp); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + boolean stopScanOffload (BluetoothDevice device, boolean isGroupOp) { + log("stopScanOffload(" + device + ", isGroupOp: " + isGroupOp + ")" ); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.stopScanOffload(device, isGroupOp); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + boolean searchforLeAudioBroadcasters (BluetoothDevice device) { + log("searchforLeAudioBroadcasters(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.searchforLeAudioBroadcasters(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + boolean stopSearchforLeAudioBroadcasters(BluetoothDevice device) { + log("stopSearchforLeAudioBroadcasters(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.stopSearchforLeAudioBroadcasters(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + boolean selectBroadcastSource (BluetoothDevice device, ScanResult scanRes, boolean isGroupOp) { + log("selectBroadcastSource(" + device + ": groupop" + isGroupOp +")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.selectBroadcastSource(device, scanRes, isGroupOp); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + void registerAppCallback(BluetoothDevice device, BleBroadcastAudioScanAssistCallback appCallback) { + log("registerAppCallback device :" + device + "appCB: " + appCallback); + Handler handler = new Handler(Looper.getMainLooper()); + + IBleBroadcastAudioScanAssistCallback wrapped = wrap(appCallback, handler); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + service.registerAppCallback(device, wrapped); + if (mAppCallbackWrappers != null) { + mAppCallbackWrappers.put(appCallback, wrapped); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return; + } + } + + void unregisterAppCallback(BluetoothDevice device, BleBroadcastAudioScanAssistCallback appCallback) { + log("unregisterAppCallback: device" + device + "appCB:" + appCallback); + // Remove callback after app was notifed. + + final IBluetoothSyncHelper service = getService(); + IBleBroadcastAudioScanAssistCallback cb = mAppCallbackWrappers.get(device); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + service.unregisterAppCallback(device, cb); + mAppCallbackWrappers.remove(appCallback); + return; + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return; + } + } + + + boolean addBroadcastSource (BluetoothDevice sinkDevice, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + log("addBroadcastSource for :" + sinkDevice + + "SourceInfo: " + srcInfo+ "isGroupOp: " + isGroupOp); + boolean ret = false; + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(sinkDevice)) { + + return service.addBroadcastSource(sinkDevice, srcInfo, isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + boolean updateBroadcastSource (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + //Same device can have more than one SourceId + log("updateBroadcastSource for :" + device + + "SourceInfo: " + srcInfo+ "isGroupOp: " + isGroupOp); + boolean ret = false; + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.updateBroadcastSource(device, + srcInfo, isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + + boolean setBroadcastCode (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + //Same device can have more than one SourceId + log("setBroadcastCode for :" + device); + log("SourceInfo: " + srcInfo+ "isGroupOp: " + isGroupOp); + boolean ret = false; + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.setBroadcastCode(device, + srcInfo, isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + boolean removeBroadcastSource (BluetoothDevice device, + byte sourceId, + boolean isGroupOp + ) { + log("removeBroadcastSource for :" + device + + "SourceId: " + sourceId + "isGroupOp: " + isGroupOp); + final IBluetoothSyncHelper service = getService(); + boolean ret = false; + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.removeBroadcastSource(device, sourceId + , isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + + List getAllBroadcastSourceInformation (BluetoothDevice device) { + log("GetAllBroadcastReceiverStates for :" + device); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.getAllBroadcastSourceInformation(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return null; + } + } + private boolean isEnabled() { + if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + BleBroadcastSourceInfo.BASS_Debug(TAG, msg); + } + +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/Android.bp b/le_audio/frameworks/base/packages/SettingsLib/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..d7a2bb17fd344c2a246edc6d229e3b95f49a16de --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/Android.bp @@ -0,0 +1,22 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +filegroup { + name: "framework-settingslib-adva-srcs", + srcs: ["src/**/*.java"], +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..677186eab178dd402217f1049c19459a622b6da8 --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; +import android.os.ParcelUuid; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import android.content.Intent; +import android.bluetooth.BleBroadcastSourceInfo; + +import com.android.settingslib.R; +import android.os.SystemProperties; +import android.os.Handler; + +import androidx.annotation.Keep; +import java.util.ArrayList; +import java.util.List; + +@Keep +public class BCProfile implements LocalBluetoothProfile { + private static final String TAG = "BCProfile"; + private static boolean V = true; + + private Context mContext; + + private BluetoothSyncHelper mService; + private boolean mIsProfileReady; + + private final CachedBluetoothDeviceManager mDeviceManager; + + static final String NAME = "BCProfile"; + private final LocalBluetoothProfileManager mProfileManager; + + // Order of this profile in device profiles list + private static final int ORDINAL = 1; + + // These callbacks run on the main thread. + private final class BassclientServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + Log.d(TAG, "BassclientService connected"); + mService = (BluetoothSyncHelper) proxy; + // We just bound to the service, so refresh the UI for any connected Bassclient devices. + //List deviceList = mService.getConnectedDevices(); + mIsProfileReady=true;//BassService connected + mProfileManager.callServiceConnectedListeners(); + } + + public void onServiceDisconnected(int profile) { + Log.d(TAG, "BassclientService disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + @Override + public int getProfileId() { + return BluetoothProfile.BC_PROFILE; + } + + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + if (enabled) { + Log.d(TAG, "BCProfile: " + device + ":" + enabled); + if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + isEnabled = mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + } + } else { + isEnabled = mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; + } + + @Override + public boolean isEnabled(BluetoothDevice device) { + if (mService == null) { + return false; + } + return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + + } + + @Override + public int getConnectionPolicy(BluetoothDevice device) { + return BluetoothProfile.CONNECTION_POLICY_ALLOWED; + } + + BCProfile(Context context, CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mContext = context; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, + new BassclientServiceListener(), BluetoothProfile.BC_PROFILE); + } + + public boolean accessProfileEnabled() { + //return true for BASS always so that + //It shows the profile preference in device details + return true; + } + + public boolean isAutoConnectable() { + if (mService == null) return false; + Log.d(TAG, "isAutoConnectable return false"); + return false; + } + + /** + * Get Scan delegator devices matching connection states{ + * @code BluetoothProfile.STATE_CONNECTED, + * @code BluetoothProfile.STATE_CONNECTING, + * @code BluetoothProfile.STATE_DISCONNECTING} + * + * @return Matching device list + */ + public List getConnectedDevices() { + return getDevicesByStates(new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + /** + * Get Scan delegator devices matching connection states{ + * @code BluetoothProfile.STATE_DISCONNECTED, + * @code BluetoothProfile.STATE_CONNECTED, + * @code BluetoothProfile.STATE_CONNECTING, + * @code BluetoothProfile.STATE_DISCONNECTING} + * + * @return Matching device list + */ + public List getConnectableDevices() { + return getDevicesByStates(new int[] { + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + private List getDevicesByStates(int[] states) { + if (mService == null) { + return new ArrayList(0); + } + return mService.getDevicesMatchingConnectionStates(states); + } + + public boolean connect(BluetoothDevice device) { + Log.d(TAG, "BCProfile Connect to device: " + device); + if (mService == null) return false; + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + Log.d(TAG, "BCProfile disonnect to device: " + device); + if (mService == null) return false; + // Downgrade priority as user is disconnecting the Bassclient. + if (mService.getConnectionPolicy(device) > BluetoothProfile.PRIORITY_ON){ + mService.setConnectionPolicy(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + return mService.getConnectionPolicy(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getConnectionPolicy(device) != + BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, + BluetoothProfile.CONNECTION_POLICY_ALLOWED); + } + } else { + mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); + } + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_bc; + } + + public BleBroadcastAudioScanAssistManager getBSAManager(BluetoothDevice device, + BleBroadcastAudioScanAssistCallback callback) { + if (mService == null) { + Log.d(TAG, "getBroadcastAudioScanAssistManager: service is null"); + return null; + } + return mService.getBleBroadcastAudioScanAssistManager(device, callback); + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_bc_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_bc_profile_summary_connected; + + default: + return BluetoothUtils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return com.android.internal.R.drawable.ic_bt_hearing_aid; + } + + protected void finalize() { + Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.BC_PROFILE, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up BAss client proxy", t); + } + } + } + + static boolean isBCSupported() { + boolean isBCSupported = SystemProperties.getBoolean("persist.vendor.service.bt.bc", true); + Log.d(TAG, "BassClientProfile: isBCSupported returns " + isBCSupported); + return isBCSupported; + } + + static public boolean isBASeeker(BluetoothDevice device) { + //always send true + boolean isSeeker = SystemProperties.getBoolean("persist.vendor.service.bt.baseeker", false); + ParcelUuid[] uuids = null; + if (device != null) { + uuids = device.getUuids(); + } + ParcelUuid sd = ParcelUuid.fromString("0000184F-0000-1000-8000-00805F9B34FB"); + if (isBCSupported()) { + if (uuids != null) { + for (ParcelUuid uid : uuids) { + if (uid.equals(sd)) { + Log.d(TAG, "SD uuid present"); + isSeeker = true; + } + } + } + } + Log.d(TAG,"isBASeeker returns:" + isSeeker); + return isSeeker; + } + +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..d1d9c013cf7b64267d6ead46a6db8a9b137f5adc --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java @@ -0,0 +1,180 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothBroadcast; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import com.android.settingslib.R; +import androidx.annotation.Keep; + +/** + * BroadcastProfile handles Bluetooth Broadcast profile. + */ +@Keep +public final class BroadcastProfile implements LocalBluetoothProfile { + private static final String TAG = "BroadcastProfile"; + private static boolean V = true; + + private BluetoothBroadcast mService; + private boolean mIsProfileReady = false; + + static final String NAME = "Broadcast"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 0; + + // These callbacks run on the main thread. + private final class BroadcastListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.BROADCAST) { + if (V) Log.d(TAG,"Bluetooth Broadcast service connected"); + mService = (BluetoothBroadcast) proxy; + mIsProfileReady = true; + } + } + + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.BROADCAST) { + if (V) Log.d(TAG,"Bluetooth Broadcast service disconnected"); + mIsProfileReady = false; + } + } + } + + public boolean isProfileReady() { + Log.d(TAG,"isProfileReady = " + mIsProfileReady); + return mIsProfileReady; + } + + @Override + public int getProfileId() { + Log.d(TAG,"getProfileId"); + return BluetoothProfile.BROADCAST; + } + + BroadcastProfile(Context context) { + Log.d(TAG,"BroadcastProfile constructor"); + BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, + new BroadcastListener(), BluetoothProfile.BROADCAST); + } + + public boolean accessProfileEnabled() { + Log.d(TAG,"accessProfileEnabled"); + return false; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + return false; + } + + public boolean disconnect(BluetoothDevice device) { + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isEnabled(BluetoothDevice device) { + return false; + } + + public int getConnectionPolicy(BluetoothDevice device) { + return CONNECTION_POLICY_FORBIDDEN; + } + + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + return false;//CONNECTION_POLICY_ALLOWED; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + } + public int getPreferred(BluetoothDevice device) { + return BluetoothProfile.PRIORITY_OFF; + } + public boolean isPreferred(BluetoothDevice device) { + return false; + } + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_broadcast; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return 0; + } + + public int getDrawableResource(BluetoothClass btClass) { + return 0; + } + + public boolean setEncryption(boolean enable, int enc_len, boolean use_existing) { + Log.d(TAG,"setEncryption"); + return mService.SetEncryption(enable, enc_len, use_existing); + } + + public byte[] getEncryptionKey() { + Log.d(TAG,"getEncryptionKey"); + return mService.GetEncryptionKey(); + } + + public int getBroadcastStatus() { + Log.d(TAG,"getBroadcastStatus"); + return mService.GetBroadcastStatus(); + } + + public boolean setBroadcastMode(boolean enable) { + Log.d(TAG,"setBroadcastMode"); + return mService.SetBroadcastMode(enable); + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy + (BluetoothProfile.BROADCAST, mService); + mService = null; + } catch (Throwable t) { + Log.w(TAG, "Error cleaning up Broadcast proxy", t); + } + } + } +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..054623e9235c72a28831bde9941eab44601386d3 --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import android.content.Intent; +import android.bluetooth.BleBroadcastSourceInfo; +import android.os.Handler; + +public class BroadcastSourceInfoHandler implements BluetoothEventManager.Handler { + private static final String TAG = "BroadcastSourceInfoHandler"; + private static final boolean V = Log.isLoggable(TAG, Log.VERBOSE); + private final CachedBluetoothDeviceManager mDeviceManager; + BroadcastSourceInfoHandler(CachedBluetoothDeviceManager deviceManager + ) { + mDeviceManager = deviceManager; + } + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + if (device == null) { + Log.w(TAG, "BroadcastSourceInfoHandler: device is null"); + return; + } + + final String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "BroadcastSourceInfoHandler: action is null"); + return; + } + BleBroadcastSourceInfo sourceInfo = intent.getParcelableExtra( + BleBroadcastSourceInfo.EXTRA_SOURCE_INFO); + + int sourceInfoIdx = intent.getIntExtra( + BleBroadcastSourceInfo.EXTRA_SOURCE_INFO_INDEX, + BluetoothAdapter.ERROR); + + int maxNumOfsrcInfo = intent.getIntExtra( + BleBroadcastSourceInfo.EXTRA_MAX_NUM_SOURCE_INFOS, + BluetoothAdapter.ERROR); + if (V) { + Log.d(TAG, "Rcved :BCAST_RECEIVER_STATE Intent for : " + device); + Log.d(TAG, "Rcvd BroadcastSourceInfo index=" + sourceInfoIdx); + Log.d(TAG, "Rcvd max num of source Info=" + maxNumOfsrcInfo); + Log.d(TAG, "Rcvd BroadcastSourceInfo=" + sourceInfo); + } + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + VendorCachedBluetoothDevice vDevice = + VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(cachedDevice, null); + if (vDevice != null) { + vDevice.onBroadcastReceiverStateChanged(sourceInfo, + sourceInfoIdx, maxNumOfsrcInfo); + cachedDevice.dispatchAttributesChanged(); + } else { + Log.e(TAG, "No vCachedDevice created for this Device"); + } + } +}; diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java new file mode 100644 index 0000000000000000000000000000000000000000..f1d0afd9b9e5ef8d15035a301e96bf0ca5e91aae --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import android.bluetooth.le.ScanResult; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.content.Context; +import android.content.SharedPreferences; +import java.util.IdentityHashMap; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.SystemClock; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import java.lang.Integer; + +import android.os.SystemProperties; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.util.ArrayUtils; +import com.android.settingslib.R; +import com.android.settingslib.Utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * VendorCachedBluetoothDevice represents a remote Bluetooth device. It contains + * attributes of the device (such as the address, name, RSSI, etc.) and + * functionality that can be performed on the device (connect, pair, disconnect, + * etc.). + */ +public class VendorCachedBluetoothDevice extends CachedBluetoothDevice { + private static final String TAG = "VendorCachedBluetoothDevice"; + private static final boolean V = Log.isLoggable(TAG, Log.VERBOSE); + private ScanResult mScanRes = null; + private BleBroadcastAudioScanAssistManager mScanAssistManager; + static private Map mBleBroadcastReceiverStates + = new HashMap(); + private LocalBluetoothProfileManager mProfileManager = null; + static private Map mVcbdEntries = new IdentityHashMap(); + + public static VendorCachedBluetoothDevice getVendorCachedBluetoothDevice( + CachedBluetoothDevice cachedDevice, + LocalBluetoothProfileManager profileManager) { + VendorCachedBluetoothDevice vCbd = null; + if (mVcbdEntries != null) { + vCbd = mVcbdEntries.get(cachedDevice); + } + //dont create new instance if profileMgr is null + if (vCbd == null && profileManager != null) { + vCbd = new VendorCachedBluetoothDevice(cachedDevice, + profileManager); + Log.d(TAG, "getVendorCachedBluetoothDevice: created new Instance"); + mVcbdEntries.put(cachedDevice, vCbd); + } + return vCbd; + } + + VendorCachedBluetoothDevice(CachedBluetoothDevice cachedDevice,LocalBluetoothProfileManager profileManager) { + super(cachedDevice); + mProfileManager = profileManager; + mBleBroadcastReceiverStates = new HashMap(); + InitializeSAManager(); + } + + VendorCachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + super(context, profileManager, device); + mProfileManager = profileManager; + mBleBroadcastReceiverStates = new HashMap(); + InitializeSAManager(); + } + + /** + * Describes the current device and profile for logging. + * + * @param profile Profile to describe + * @return Description of the device and profile + */ + private String describe(LocalBluetoothProfile profile) { + StringBuilder sb = new StringBuilder(); + sb.append("Address:").append(mDevice); + if (profile != null) { + sb.append(" Profile:").append(profile); + } + + return sb.toString(); + } + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (V) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device=" + mDevice + + ", newProfileState " + newProfileState); + } + if (profile instanceof BCProfile + && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + cleanUpSAMananger(); + super.dispatchAttributesChanged(); + } + } + + private BleBroadcastAudioScanAssistCallback mScanAssistCallback = new BleBroadcastAudioScanAssistCallback() { + public void onBleBroadcastSourceFound(ScanResult res) { + if (V) { + Log.d(TAG, "onBleBroadcastSourceFound" + res.getDevice()); + } + setScanResult(res); + }; + + public void onBleBroadcastAudioSourceAdded(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + + public void onBleBroadcastSourceSelected( int status, + List broadcastSourceIndicies) { + }; + + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + }; + + public BleBroadcastAudioScanAssistManager getScanAssistManager() + { InitializeSAManager(); + return mScanAssistManager; + } + + void InitializeSAManager() { + BCProfile bcProfile = (BCProfile)mProfileManager.getBCProfile(); + mScanAssistManager = bcProfile.getBSAManager( + mDevice, mScanAssistCallback); + } + + void cleanUpSAMananger() { + mScanAssistManager = null; + if (mBleBroadcastReceiverStates != null) { + mBleBroadcastReceiverStates.clear(); + } + } + + void updateBroadcastreceiverStates(BleBroadcastSourceInfo srcInfo, int index, + int maxSourceInfosNum) { + BleBroadcastSourceInfo entry = mBleBroadcastReceiverStates.get(index); + if (entry != null) { + Log.d(TAG, "updateBroadcastreceiverStates: Replacing receiver State Information"); + mBleBroadcastReceiverStates.replace(index, srcInfo); + } else { + mBleBroadcastReceiverStates.put(index, srcInfo); + } + super.dispatchAttributesChanged(); + } + + public int getNumberOfBleBroadcastReceiverStates() { + int ret = 0; + if (mScanAssistManager == null) { + InitializeSAManager(); + if (mScanAssistManager == null) { + return ret; + } + } + List srcInfo = mScanAssistManager.getAllBroadcastSourceInformation(); + if (srcInfo != null) { + ret = srcInfo.size(); + } + if (V) { + Log.d(TAG, "getNumberOfBleBroadcastReceiverStates:"+ ret); + } + return ret; + } + + public Map getAllBleBroadcastreceiverStates() { + if (mScanAssistManager == null) { + InitializeSAManager(); + if (mScanAssistManager == null) { + Log.e(TAG, "SA Manager cant be initialized"); + return null; + } + } + List srcInfos = mScanAssistManager.getAllBroadcastSourceInformation(); + if (srcInfos == null) { + Log.e(TAG, "getAllBleBroadcastreceiverStates: no src Info"); + return null; + } + for (int i=0; i srcInfos = mScanAssistManager.getAllBroadcastSourceInformation(); + if (srcInfos == null) { + Log.e(TAG, "isBroadcastAudioSynced: no src Info"); + return false; + } + for (int i=0; i +#include + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecPriority; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecBPS; +using bluetooth::bap::pacs::CodecChannelMode; + +namespace android { +static jmethodID method_onConnectionStateChanged; +static jmethodID method_onAudioStateChanged; +static jmethodID method_onCodecConfigChanged; + +static struct { + jclass clazz; + jmethodID constructor; + jmethodID getCodecType; + jmethodID getCodecPriority; + jmethodID getSampleRate; + jmethodID getBitsPerSample; + jmethodID getChannelMode; + jmethodID getCodecSpecific1; + jmethodID getCodecSpecific2; + jmethodID getCodecSpecific3; + jmethodID getCodecSpecific4; +} android_bluetooth_BluetoothCodecConfig; + +static const btacm_initiator_interface_t* sBluetoothAcmInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +static void btacm_connection_state_callback(const RawAddress& bd_addr, + btacm_connection_state_t state, uint16_t contextType) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, sizeof(RawAddress), + reinterpret_cast(bd_addr.address)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, + addr.get(), (jint)state, (jint)contextType); +} + +static void btacm_audio_state_callback(const RawAddress& bd_addr, + btacm_audio_state_t state, uint16_t contextType) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, sizeof(RawAddress), + reinterpret_cast(bd_addr.address)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, + addr.get(), (jint)state, (jint)contextType); +} + +static void btacm_audio_config_callback( + const RawAddress& bd_addr, CodecConfig codec_config, + std::vector codecs_local_capabilities, + std::vector codecs_selectable_capabilities, uint16_t contextType) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + jobject codecConfigObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)codec_config.codec_type, (jint)codec_config.codec_priority, + (jint)codec_config.sample_rate, (jint)codec_config.bits_per_sample, + (jint)codec_config.channel_mode, (jlong)codec_config.codec_specific_1, + (jlong)codec_config.codec_specific_2, + (jlong)codec_config.codec_specific_3, + (jlong)codec_config.codec_specific_4); + + jsize i = 0; + jobjectArray local_capabilities_array = sCallbackEnv->NewObjectArray( + (jsize)codecs_local_capabilities.size(), + android_bluetooth_BluetoothCodecConfig.clazz, nullptr); + for (auto const& cap : codecs_local_capabilities) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(local_capabilities_array, i++, capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + i = 0; + jobjectArray selectable_capabilities_array = sCallbackEnv->NewObjectArray( + (jsize)codecs_selectable_capabilities.size(), + android_bluetooth_BluetoothCodecConfig.clazz, nullptr); + for (auto const& cap : codecs_selectable_capabilities) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(selectable_capabilities_array, i++, + capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(RawAddress::kLength)); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, RawAddress::kLength, + reinterpret_cast(bd_addr.address)); + + sCallbackEnv->CallVoidMethod( + mCallbacksObj, method_onCodecConfigChanged, addr.get(), codecConfigObj, + local_capabilities_array, selectable_capabilities_array, (jint)contextType); +} + +static btacm_initiator_callbacks_t sBluetoothAcmCallbacks = { + sizeof(sBluetoothAcmCallbacks), + btacm_connection_state_callback, + btacm_audio_state_callback, + btacm_audio_config_callback +}; + +static void classInitNative(JNIEnv* env, jclass clazz) { + jclass jniBluetoothCodecConfigClass = + env->FindClass("android/bluetooth/BluetoothCodecConfig"); + android_bluetooth_BluetoothCodecConfig.constructor = + env->GetMethodID(jniBluetoothCodecConfigClass, "", "(IIIIIJJJJ)V"); + android_bluetooth_BluetoothCodecConfig.getCodecType = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecPriority = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I"); + android_bluetooth_BluetoothCodecConfig.getSampleRate = + env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I"); + android_bluetooth_BluetoothCodecConfig.getBitsPerSample = + env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I"); + android_bluetooth_BluetoothCodecConfig.getChannelMode = + env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific1 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific2 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific3 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific4 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J"); + + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "([BII)V"); + + method_onAudioStateChanged = + env->GetMethodID(clazz, "onAudioStateChanged", "([BII)V"); + + method_onCodecConfigChanged = + env->GetMethodID(clazz, "onCodecConfigChanged", + "([BLandroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;I)V"); + + ALOGI("%s: succeeds", __func__); +} + +static std::vector prepareCodecPreferences( + JNIEnv* env, jobject object, jobjectArray codecConfigArray) { + std::vector codec_preferences; + + int numConfigs = env->GetArrayLength(codecConfigArray); + for (int i = 0; i < numConfigs; i++) { + jobject jcodecConfig = env->GetObjectArrayElement(codecConfigArray, i); + if (jcodecConfig == nullptr) continue; + if (!env->IsInstanceOf(jcodecConfig, + android_bluetooth_BluetoothCodecConfig.clazz)) { + ALOGE("%s: Invalid BluetoothCodecConfig instance", __func__); + continue; + } + jint codecType = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType); + jint codecPriority = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecPriority); + jint sampleRate = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getSampleRate); + jint bitsPerSample = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getBitsPerSample); + jint channelMode = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getChannelMode); + jlong codecSpecific1 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific1); + jlong codecSpecific2 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific2); + jlong codecSpecific3 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific3); + jlong codecSpecific4 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific4); + + CodecConfig codec_config = { + .codec_type = static_cast(codecType), + .codec_priority = + static_cast(codecPriority), + .sample_rate = static_cast(sampleRate), + .bits_per_sample = + static_cast(bitsPerSample), + .channel_mode = + static_cast(channelMode), + .codec_specific_1 = codecSpecific1, + .codec_specific_2 = codecSpecific2, + .codec_specific_3 = codecSpecific3, + .codec_specific_4 = codecSpecific4}; + + codec_preferences.push_back(codec_config); + } + return codec_preferences; +} + +static void initNative(JNIEnv* env, jobject object, + jint maxConnectedAudioDevices, + jobjectArray codecConfigArray) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothAcmInterface != nullptr) { + ALOGW("%s: Cleaning up ACM Interface before initializing...", __func__); + sBluetoothAcmInterface->cleanup(); + sBluetoothAcmInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + ALOGW("%s: Cleaning up ACM callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for ACM Callbacks", __func__); + return; + } + + android_bluetooth_BluetoothCodecConfig.clazz = (jclass)env->NewGlobalRef( + env->FindClass("android/bluetooth/BluetoothCodecConfig")); + if (android_bluetooth_BluetoothCodecConfig.clazz == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class", + __func__); + return; + } + + sBluetoothAcmInterface = + (btacm_initiator_interface_t*)btInf->get_profile_interface( + BT_PROFILE_ACM_ID); + if (sBluetoothAcmInterface == nullptr) { + ALOGE("%s: Failed to get Bluetooth ACM Interface", __func__); + return; + } + + std::vector codec_priorities = + prepareCodecPreferences(env, object, codecConfigArray); + + bt_status_t status = sBluetoothAcmInterface->init( + &sBluetoothAcmCallbacks, maxConnectedAudioDevices, codec_priorities); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize Bluetooth ACM, status: %d", __func__, + status); + sBluetoothAcmInterface = nullptr; + return; + } +} + +static void cleanupNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothAcmInterface != nullptr) { + sBluetoothAcmInterface->cleanup(); + sBluetoothAcmInterface = nullptr; + } + + env->DeleteGlobalRef(android_bluetooth_BluetoothCodecConfig.clazz); + android_bluetooth_BluetoothCodecConfig.clazz = nullptr; + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean connectAcmNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType, + jint profileType, jint preferredContext) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + bt_status_t status = sBluetoothAcmInterface->connect(bd_addr, contextType, + profileType, preferredContext); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM connection, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean disconnectAcmNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + bt_status_t status = sBluetoothAcmInterface->disconnect(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM disconnection, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean startStreamNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothAcmInterface->start_stream(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM set_active_device, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean stopStreamNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothAcmInterface->stop_stream(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM set_active_device, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothAcmInterface->set_active_device(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM set_active_device, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object, + jbyteArray address, + jobjectArray codecConfigArray, + jint contextType, jint preferredContext) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + std::vector codec_preferences = + prepareCodecPreferences(env, object, codecConfigArray); + + bt_status_t status = + sBluetoothAcmInterface->config_codec(bd_addr, codec_preferences, contextType, preferredContext); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed codec configuration, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean ChangeCodecConfigPreferenceNative(JNIEnv* env, jobject object, + jbyteArray address, + jstring message) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + const char* c_msg = env->GetStringUTFChars(message, NULL); + bt_status_t status = + sBluetoothAcmInterface->change_config_codec(bd_addr, (char*)c_msg); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed codec configuration, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "(I[Landroid/bluetooth/BluetoothCodecConfig;)V", + (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectAcmNative", "([BIII)Z", (void*)connectAcmNative}, + {"disconnectAcmNative", "([BI)Z", (void*)disconnectAcmNative}, + {"startStreamNative", "([BI)Z", (void*)startStreamNative}, + {"stopStreamNative", "([BI)Z", (void*)stopStreamNative}, + {"setActiveDeviceNative", "([BI)Z", (void*)setActiveDeviceNative}, + {"setCodecConfigPreferenceNative", + "([B[Landroid/bluetooth/BluetoothCodecConfig;II)Z", + (void*)setCodecConfigPreferenceNative}, + {"ChangeCodecConfigPreferenceNative", + "([BLjava/lang/String;)Z", + (void*)ChangeCodecConfigPreferenceNative}, +}; + +int register_com_android_bluetooth_acm(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/acm/AcmNativeInterface", sMethods, + NELEM(sMethods)); +} +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6ffa2daff652556adb13b4476201048f0cb8b7f5 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp @@ -0,0 +1,194 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#define LOG_TAG "BluetoothAPM_Jni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_apm.h" +#include "utils/Log.h" + +#include +#include + +namespace android { +static jmethodID method_onGetActiveprofileCallback; + +static const bt_apm_interface_t* sBluetoothApmInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + + +static int btapm_active_profile_callback(const RawAddress& bd_addr, uint16_t audio_type) +{ + ALOGI("%s", __func__); + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return -1; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return -1; + } + + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, sizeof(RawAddress), + reinterpret_cast(bd_addr.address)); + return sCallbackEnv->CallIntMethod(mCallbacksObj, method_onGetActiveprofileCallback, + addr.get(), (jint)audio_type); +} + + +static btapm_initiator_callbacks_t sBluetoothApmCallbacks = { + sizeof(sBluetoothApmCallbacks), + btapm_active_profile_callback +}; + +static void classInitNative(JNIEnv* env, jclass clazz) { + + ALOGI("%s: succeeds", __func__); + method_onGetActiveprofileCallback = + env->GetMethodID(clazz, "getActiveProfile", "([BI)I"); +} + +static bool initNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return JNI_FALSE; + } + + if (sBluetoothApmInterface != nullptr) { + ALOGW("%s: Cleaning up APM Interface before initializing...", __func__); + sBluetoothApmInterface->cleanup(); + sBluetoothApmInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + ALOGW("%s: Cleaning up APM callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for APM Callbacks", __func__); + return JNI_FALSE; + } + + sBluetoothApmInterface = + (bt_apm_interface_t*)btInf->get_profile_interface( + BT_APM_MODULE_ID); + if (sBluetoothApmInterface == nullptr) { + ALOGE("%s: Failed to get Bluetooth APM Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothApmInterface->init(&sBluetoothApmCallbacks); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize Bluetooth APM, status: %d", __func__, + status); + sBluetoothApmInterface = nullptr; + return JNI_FALSE; + } + return JNI_TRUE; +} + +static void cleanupNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothApmInterface != nullptr) { + sBluetoothApmInterface->cleanup(); + sBluetoothApmInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean activeDeviceUpdateNative(JNIEnv* env, jobject object, + jbyteArray address, jint profile, jint audio_type) { + ALOGI("%s: sBluetoothApmInterface: %p", __func__, sBluetoothApmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothApmInterface) { + ALOGE("%s: Failed to get the Bluetooth APM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothApmInterface->active_device_change(bd_addr, profile, audio_type); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed APM active_device_change, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setContentControlNative(JNIEnv* env, jobject object, + jint content_control_id, jint profile) { + ALOGI("%s: sBluetoothApmInterface: %p", __func__, sBluetoothApmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothApmInterface) { + ALOGE("%s: Failed to get the Bluetooth APM Interface", __func__); + return JNI_FALSE; + } + + bt_status_t status = sBluetoothApmInterface->set_content_control_id(content_control_id, profile); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed APM content control update, status: %d", __func__, status); + } + + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"activeDeviceUpdateNative", "([BII)Z", (void*)activeDeviceUpdateNative}, + {"setContentControlNative", "(II)Z", (void*)setContentControlNative}, +}; + +int register_com_android_bluetooth_apm(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/apm/ApmNativeInterface", sMethods, + NELEM(sMethods)); +} +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75ea08504d14406937f661b2d356b7765c6a9878 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp @@ -0,0 +1,470 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#define LOG_TAG "BluetoothBapBroadcastServiceJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_av.h" +#include "hardware/bt_bap_ba.h" +#include "utils/Log.h" + +#include +#include + +namespace android { +static jmethodID method_onBroadcastStateChanged; +static jmethodID method_onAudioStateChanged; +static jmethodID method_onCodecConfigChanged; +//static jmethodID method_onIsoDataPathChanged; +static jmethodID method_onEncryptionKeyGenerated; +static jmethodID method_onSetupBIG; +static jmethodID method_onBroadcastIdGenerated; +static struct { + jclass clazz; + jmethodID constructor; + jmethodID getCodecType; + jmethodID getCodecPriority; + jmethodID getSampleRate; + jmethodID getBitsPerSample; + jmethodID getChannelMode; + jmethodID getCodecSpecific1; + jmethodID getCodecSpecific2; + jmethodID getCodecSpecific3; + jmethodID getCodecSpecific4; +} android_bluetooth_BluetoothCodecConfig; + +static const btbap_broadcast_interface_t* sBluetoothBapBroadcastInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +static void btbap_broadcast_state_callback(jint adv_id, btbap_broadcast_state_t state) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + ALOGI("%s: lock acquired", __func__); + CallbackEnv sCallbackEnv(__func__); + ALOGI("%s:got callback env", __func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { + ALOGI("%s:either callback is not valid or callbackobj is null", __func__); + return; + } + ALOGI("%s: calling method to native interface", __func__); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBroadcastStateChanged, adv_id, (jint)state); +} + +static void btbap_broadcast_audio_state_callback(jint big_handle, btbap_broadcast_audio_state_t state) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, big_handle, (jint)state); +} + +static void btbap_broadcast_audio_config_callback(jint adv_id, btav_a2dp_codec_config_t codec_config, + std::vector codec_capabilities) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + jobject codecConfigObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)codec_config.codec_type, (jint)codec_config.codec_priority, + (jint)codec_config.sample_rate, (jint)codec_config.bits_per_sample, + (jint)codec_config.channel_mode, (jlong)codec_config.codec_specific_1, + (jlong)codec_config.codec_specific_2, + (jlong)codec_config.codec_specific_3, + (jlong)codec_config.codec_specific_4); + + jsize i = 0; + jobjectArray local_capabilities_array = sCallbackEnv->NewObjectArray( + (jsize)codec_capabilities.size(), + android_bluetooth_BluetoothCodecConfig.clazz, nullptr); + for (auto const& cap : codec_capabilities) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(local_capabilities_array, i++, capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCodecConfigChanged, + adv_id, codecConfigObj, local_capabilities_array); +} + +/*static void btbap_broadcast_iso_datapath_callback(jint big_handle, jint state) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onIsoDataPathChanged, big_handle, state); +}*/ + +static void btbap_broadcast_enckey_callback(std::string pin) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + /*ScopedLocalRef pinkey( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(pin))); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + + sCallbackEnv->SetByteArrayRegion( + pinkey.get(), 0, sizeof(pin), + reinterpret_cast(pin));*/ + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onEncryptionKeyGenerated, + sCallbackEnv->NewStringUTF(pin.c_str())); +} + +static void btbap_broadcast_setup_big_callback(jint setup, jint adv_id, jint big_handle, + jint num_bises, std::vector bis_handles) { + ALOGI("%s", __func__); + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + ScopedLocalRef jc(sCallbackEnv.get(), sCallbackEnv->NewCharArray(bis_handles.size())); + sCallbackEnv->SetCharArrayRegion(jc.get(), 0, bis_handles.size(), (jchar*) bis_handles.data()); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetupBIG, setup, adv_id, big_handle, num_bises, jc.get()); +} + +static void btbap_broadcast_bid_callback(std::vector broadcast_id) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + ALOGI("%s: broadcast_id size = %d",__func__,broadcast_id.size()); + ScopedLocalRef jb(sCallbackEnv.get(), sCallbackEnv->NewByteArray(broadcast_id.size())); + if (!jb.get()) { + ALOGI("%s:Failed to allocate byte array"); + return; + } + sCallbackEnv->SetByteArrayRegion(jb.get(), 0, broadcast_id.size(), (jbyte*) broadcast_id.data()); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBroadcastIdGenerated, jb.get()); +} + +static btbap_broadcast_callbacks_t sBluetoothBapBroadcastCallbacks = { + sizeof(sBluetoothBapBroadcastCallbacks), + btbap_broadcast_state_callback, + btbap_broadcast_audio_state_callback, + btbap_broadcast_audio_config_callback, + //btbap_broadcast_iso_datapath_callback, + btbap_broadcast_enckey_callback, + btbap_broadcast_setup_big_callback, + btbap_broadcast_bid_callback, +}; + +static void classInitNative(JNIEnv* env, jclass clazz) { + jclass jniBluetoothCodecConfigClass = + env->FindClass("android/bluetooth/BluetoothCodecConfig"); + android_bluetooth_BluetoothCodecConfig.constructor = + env->GetMethodID(jniBluetoothCodecConfigClass, "", "(IIIIIJJJJ)V"); + android_bluetooth_BluetoothCodecConfig.getCodecType = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecPriority = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I"); + android_bluetooth_BluetoothCodecConfig.getSampleRate = + env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I"); + android_bluetooth_BluetoothCodecConfig.getBitsPerSample = + env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I"); + android_bluetooth_BluetoothCodecConfig.getChannelMode = + env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific1 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific2 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific3 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific4 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J"); + + method_onBroadcastStateChanged = + env->GetMethodID(clazz, "onBroadcastStateChanged", "(II)V"); + + method_onAudioStateChanged = + env->GetMethodID(clazz, "onAudioStateChanged", "(II)V"); + + method_onCodecConfigChanged = + env->GetMethodID(clazz, "onCodecConfigChanged", + "(ILandroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;)V"); + +// method_onIsoDataPathChanged = +// env->GetMethodID(clazz, "onIsoDataPathChanged","(II)V"); + method_onEncryptionKeyGenerated = + env->GetMethodID(clazz, "onEncryptionKeyGenerated", "(Ljava/lang/String;)V"); + + method_onSetupBIG = env->GetMethodID(clazz, "onSetupBIG", "(IIII[C)V"); + method_onBroadcastIdGenerated = env->GetMethodID(clazz, "onBroadcastIdGenerated", "([B)V"); + + ALOGI("%s: succeeds", __func__); +} +static btav_a2dp_codec_config_t prepare_codec_config( + JNIEnv* env, jobject object,jobject jcodecConfig) { + + /*if (!env->IsInstanceOf(jcodecConfig, + android_bluetooth_BluetoothCodecConfig.clazz)) { + ALOGE("%s: Invalid BluetoothCodecConfig instance", __func__); + return ((btav_a2dp_codec_config_t)NULL); + }*/ + jint codecType = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType); + jint codecPriority = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecPriority); + jint sampleRate = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getSampleRate); + jint bitsPerSample = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getBitsPerSample); + jint channelMode = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getChannelMode); + jlong codecSpecific1 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific1); + jlong codecSpecific2 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific2); + jlong codecSpecific3 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific3); + jlong codecSpecific4 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific4); + + btav_a2dp_codec_config_t codec_config = { + .codec_type = static_cast(codecType), + .codec_priority = + static_cast(codecPriority), + .sample_rate = static_cast(sampleRate), + .bits_per_sample = + static_cast(bitsPerSample), + .channel_mode = + static_cast(channelMode), + .codec_specific_1 = codecSpecific1, + .codec_specific_2 = codecSpecific2, + .codec_specific_3 = codecSpecific3, + .codec_specific_4 = codecSpecific4}; + return codec_config; +} + +static void initNative(JNIEnv* env, jobject object, + jint maxBroadcast, jobject codecConfig, jint mode) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothBapBroadcastInterface != nullptr) { + ALOGW("%s: Cleaning up BapBroadcast Interface before initializing...", __func__); + sBluetoothBapBroadcastInterface->cleanup(); + sBluetoothBapBroadcastInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + ALOGW("%s: Cleaning up BapBroadcast callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for bap broadcast Callbacks", __func__); + return; + } + + android_bluetooth_BluetoothCodecConfig.clazz = (jclass)env->NewGlobalRef( + env->FindClass("android/bluetooth/BluetoothCodecConfig")); + if (android_bluetooth_BluetoothCodecConfig.clazz == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class", + __func__); + return; + } + + sBluetoothBapBroadcastInterface = + (btbap_broadcast_interface_t*)btInf->get_profile_interface( + BT_PROFILE_BAP_BROADCAST_ID); + if (sBluetoothBapBroadcastInterface == nullptr) { + ALOGE("%s: Failed to get Bluetooth BapBroadcast Interface", __func__); + return; + } + btav_a2dp_codec_config_t codec_config = + prepare_codec_config(env, object, codecConfig); + /*if (codec_config == NULL) { + ALOGE("%s:Invalid codec config",__func__); + return; + }*/ + bt_status_t status = sBluetoothBapBroadcastInterface->init( + &sBluetoothBapBroadcastCallbacks, maxBroadcast, codec_config, mode); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__, + status); + sBluetoothBapBroadcastInterface = nullptr; + return; + } +} + +static void cleanupNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothBapBroadcastInterface != nullptr) { + sBluetoothBapBroadcastInterface->cleanup(); + sBluetoothBapBroadcastInterface = nullptr; + } + + env->DeleteGlobalRef(android_bluetooth_BluetoothCodecConfig.clazz); + android_bluetooth_BluetoothCodecConfig.clazz = nullptr; + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jboolean enable, jint adv_id) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->set_broadcast_active(enable, adv_id); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean enableBroadcastNative(JNIEnv* env, jobject object, jobject codecConfig) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + btav_a2dp_codec_config_t codec_config = + prepare_codec_config(env, object, codecConfig); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->enable_broadcast(codec_config); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean disableBroadcastNative(JNIEnv* env, jobject object, jint adv_id) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->disable_broadcast(adv_id); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setupAudioPathNative(JNIEnv* env, jobject object, jboolean enable,jint adv_id, + jint big_handle, jint num_bises, jintArray bises) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + jint* bis_handles = env->GetIntArrayElements(bises, NULL); + bt_status_t status = sBluetoothBapBroadcastInterface->setup_audiopath(enable, adv_id, big_handle, num_bises, bis_handles); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jstring getEncryptionKeyNative(JNIEnv* env, jobject object) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + std::string stdstr = sBluetoothBapBroadcastInterface->get_encryption_key(); + return env->NewStringUTF(stdstr.c_str()); +} + +static jboolean setEncryptionKeyNative(JNIEnv* env, jobject object, jboolean enabled, jint length) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->set_encryption(enabled, length); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object, + jint adv_handle, jobject codecConfig) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + btav_a2dp_codec_config_t codec_config = + prepare_codec_config(env, object, codecConfig); + bt_status_t status = sBluetoothBapBroadcastInterface->codec_config_change(adv_handle, codec_config); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "(ILandroid/bluetooth/BluetoothCodecConfig;I)V", + (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"setActiveDeviceNative", "(ZI)Z", (void*)setActiveDeviceNative}, + {"enableBroadcastNative", "(Landroid/bluetooth/BluetoothCodecConfig;)Z", (void*)enableBroadcastNative}, + {"disableBroadcastNative", "(I)Z", (void*)disableBroadcastNative}, + {"getEncryptionKeyNative", "()Ljava/lang/String;", (void*)getEncryptionKeyNative}, + {"setEncryptionKeyNative", "(ZI)Z", (void*)setEncryptionKeyNative}, + {"setupAudioPathNative", "(ZIII[I)Z", (void*)setupAudioPathNative}, + {"setCodecConfigPreferenceNative", + "(ILandroid/bluetooth/BluetoothCodecConfig;)Z", + (void*)setCodecConfigPreferenceNative}, +}; + +int register_com_android_bluetooth_bap_broadcast(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/broadcast/BroadcastNativeInterface", sMethods, + NELEM(sMethods)); +} + +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f763310d9776e08c55bfd1ae43da6ec027a7864f --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp @@ -0,0 +1,76 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth_ext.h" + +namespace android { + + int register_com_android_bluetooth_adv_audio_profiles(JNIEnv* env) { + ALOGE("%s", __func__); + + int status = android::register_com_android_bluetooth_csip_client(env); + if (status < 0) { + ALOGE("jni csip registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_acm(env); + if (status < 0) { + ALOGE("jni acm registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_apm(env); + if (status < 0) { + ALOGE("jni APM registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_bap_broadcast(env); + if (status < 0) { + ALOGE("jni bap broadcast registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_vcp_controller(env); + if (status < 0) { + ALOGE("jni vcp controller registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_pacs_client(env); + if (status < 0) { + ALOGE("jni pacs client registration failure: %d", status); + return JNI_ERR; + } + status = android::register_com_android_bluetooth_call_controller(env); + if (status < 0) { + ALOGE("jni CC registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_mcp(env); + if (status < 0) { + ALOGE("jni mcp registration failure: %d", status); + return JNI_ERR; + } + return JNI_VERSION_1_6; + } +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e8203eac2ed17159011138c93273167d63963b08 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp @@ -0,0 +1,402 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + + * Copyright 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "BluetoothCCServiceJni" + +#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bluetooth.h" +#include "hardware/bluetooth_callcontrol_callbacks.h" +#include "hardware/bluetooth_callcontrol_interface.h" + +using bluetooth::call_control::CallControllerCallbacks; +using bluetooth::call_control::CallControllerInterface; +using bluetooth::Uuid; +static CallControllerInterface* sCallControllerInterface = nullptr; + +namespace android { +static jmethodID method_CallControlInitializedCallback; +static jmethodID method_OnConnectionStateChanged; +static jmethodID method_CallControlPointChangedRequest; +static std::shared_timed_mutex interface_mutex; +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class CallControllerCallbacksImpl : public CallControllerCallbacks { + public: + ~CallControllerCallbacksImpl() = default; + void CallControlInitializedCallback(uint8_t state) override { + LOG(INFO) << __func__; + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_CallControlInitializedCallback, + (jint)state); + } + void ConnectionStateCallback(uint8_t state, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnConnectionStateChanged, + (jint)state, addr.get()); + } + + void CallControlCallback(uint8_t op, std::vector p_indices, int count, std::vector uri_data, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + ScopedLocalRef indices(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(count)); + ScopedLocalRef originate_uri( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(uri_data.size())); + if (!originate_uri.get()) { + ALOGE("Error while allocation byte array for uri data in %s", __func__); + return; + } + sCallbackEnv->SetByteArrayRegion(originate_uri.get(), 0, uri_data.size(), + (jbyte*)uri_data.data()); + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->SetIntArrayRegion(indices.get(), 0, count,(jint*)p_indices.data()); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_CallControlPointChangedRequest, + (jint)op, indices.get(), (jint)count, originate_uri.get(), addr.get()); + } +}; + +static CallControllerCallbacksImpl sCallControllerCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + method_CallControlInitializedCallback = + env->GetMethodID(clazz, "callControlInitializedCallback", "(I)V"); + method_OnConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); + method_CallControlPointChangedRequest = + env->GetMethodID(clazz, "callControlPointChangedRequest", "(I[II[B[B)V"); + + LOG(INFO) << __func__ << " : succeeds"; +} + +static void initializeNative(JNIEnv* env, jobject object, jstring uuid, + jint max_ccs_clients, jboolean inband_ringing_enabled) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (!btInf) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + jniThrowIOException(env, EINVAL); + return; + } + + if (sCallControllerInterface) { + ALOGI("%s: Cleaning up Bluetooth CallControl Interface before initializing", + __func__); + sCallControllerInterface->Cleanup(); + sCallControllerInterface = nullptr; + } + + if (mCallbacksObj) { + ALOGI("%s: Cleaning up Bluetooth CallControl callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + const char* _uuid = env->GetStringUTFChars(uuid, nullptr); + + sCallControllerInterface = + (CallControllerInterface*)btInf->get_profile_interface( + BT_PROFILE_CC_ID); + if (!sCallControllerInterface) { + ALOGW("%s: Failed to get Bluetooth CallControl Interface", __func__); + jniThrowIOException(env, EINVAL); + return; + } + bt_status_t status = + sCallControllerInterface->Init(&sCallControllerCallbacks, + bluetooth::Uuid::FromString(_uuid), max_ccs_clients, inband_ringing_enabled); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize LE audio Call control Interface, status: %d", + __func__, status); + sCallControllerInterface = nullptr; + return; + } + + env->ReleaseStringUTFChars(uuid, _uuid); + mCallbacksObj = env->NewGlobalRef(object); +} + +static void cleanupNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sCallControllerInterface != nullptr) { + sCallControllerInterface->Cleanup(); + sCallControllerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean updateBearerNameNative(JNIEnv* env, jobject object, + jstring operator_str) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + const char* operator_name = env->GetStringUTFChars(operator_str, nullptr); + bt_status_t status = + sCallControllerInterface->UpdateBearerName((uint8_t*)operator_name); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed updateBearerNameNative, status: %d", status); + } + env->ReleaseStringUTFChars(operator_str, operator_name); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateBearerTechnologyNative(JNIEnv* env, jobject object, + jint bearer_tech) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->UpdateBearerTechnology(bearer_tech); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed updateBearerTechnologyNative, status: %d", status); + } + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateSupportedBearerListNative(JNIEnv* env, jobject object, + jstring bearer_list) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + const char* list_bearer_string = env->GetStringUTFChars(bearer_list, nullptr); + bt_status_t status = sCallControllerInterface->UpdateSupportedBearerList((uint8_t*)list_bearer_string); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed updateSupportedBearerListNative, status: %d", status); + } + env->ReleaseStringUTFChars(bearer_list, list_bearer_string); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + + +static jboolean callControlPointOpcodeSupportedNative(JNIEnv* env, jobject object, + jint feature) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->CallControlOptionalOpSupported(feature); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateStatusFlagsNative(JNIEnv* env, jobject object, + jint flags) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->UpdateStatusFlags(flags); + + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateSignalStatusNative(JNIEnv* env, jobject object, + jint signal) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->UpdateSignalStatus(signal); + if (status != BT_STATUS_SUCCESS) { + ALOGE("FAILED updateSignalStatusNative, status: %d", status); + } + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} +static jboolean updateIncomingCallNative(JNIEnv* env, jobject object, + jint index, jstring uri_str) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + const char* uri = env->GetStringUTFChars(uri_str, nullptr); + sCallControllerInterface->UpdateIncomingCall(index, (uint8_t*)uri); + env->ReleaseStringUTFChars(uri_str, uri); + return JNI_TRUE; +} + +static jboolean callControlResponseNative(JNIEnv* env, jobject object, + jint op, jint index, jint status, jbyteArray address) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + bt_status_t ret_status = + sCallControllerInterface->CallControlResponse(op, index, status, *tmpraw); + if (ret_status != BT_STATUS_SUCCESS) { + ALOGE("Failed to send callControlResponseNative, status: %d", ret_status); + } + return (ret_status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jint set_id, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) return JNI_FALSE; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + sCallControllerInterface->SetActiveDevice(*tmpraw, set_id); + env->ReleaseByteArrayElements(address, addr, 0); + + return JNI_TRUE; +} + +static jboolean callStateNative(JNIEnv* env, jobject object, jint len, + jbyteArray callList) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + + jbyte* cList = env->GetByteArrayElements(callList, NULL); + if (!cList) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + uint16_t array_len = (uint16_t)env->GetArrayLength(callList); + + std::vector vect_val(cList, cList + array_len); + + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + sCallControllerInterface->CallState(len, std::move(vect_val)); + env->ReleaseByteArrayElements(callList, cList, 0); + return JNI_TRUE; +} + +static jboolean contentControlIdNative(JNIEnv* env, jobject object, + jint ccid) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) return JNI_FALSE; + + sCallControllerInterface->ContentControlId(ccid); + return JNI_TRUE; +} + +static jboolean disconnectNative(JNIEnv* env, jobject object, + jbyteArray address) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + + sCallControllerInterface->Disconnect(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initializeNative", "(Ljava/lang/String;IZ)V", (void*)initializeNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"updateBearerNameNative", "(Ljava/lang/String;)Z", (void*)updateBearerNameNative}, + {"updateBearerTechnologyNative", "(I)Z", (void*)updateBearerTechnologyNative}, + {"updateSupportedBearerListNative", "(Ljava/lang/String;)Z", (void*)updateSupportedBearerListNative}, + {"updateSignalStatusNative", "(I)Z", (void*)updateSignalStatusNative}, + {"updateStatusFlagsNative", "(I)Z", (void*)updateStatusFlagsNative}, + {"updateIncomingCallNative", "(ILjava/lang/String;)Z", (void*)updateIncomingCallNative}, + {"callControlResponseNative", "(III[B)Z", (void*)callControlResponseNative}, + {"callStateNative", "(I[B)Z", (void*)callStateNative}, + {"callControlPointOpcodeSupportedNative", "(I)Z", (void*)callControlPointOpcodeSupportedNative}, + {"setActiveDeviceNative", "(I[B)Z", (void*)setActiveDeviceNative}, + {"contentControlIdNative", "(I)Z", (void*)contentControlIdNative}, + {"disconnectNative", "([B)Z", (void*)disconnectNative}, +}; + +int register_com_android_bluetooth_call_controller(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/cc/CCNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c3a8f6d68f6c884c116112280bd6d44b8b0efa27 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp @@ -0,0 +1,409 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#define LOG_TAG "BluetoothCsipClientJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_csip.h" +#include "utils/Log.h" + +#include +#include + +using bluetooth::Uuid; + +#define UUID_PARAMS(uuid) uuid_lsb(uuid), uuid_msb(uuid) + +static uint64_t uuid_lsb(const Uuid& uuid) { + uint64_t lsb = 0; + + auto uu = uuid.To128BitBE(); + for (int i = 8; i <= 15; i++) { + lsb <<= 8; + lsb |= uu[i]; + } + + return lsb; +} + +static uint64_t uuid_msb(const Uuid& uuid) { + uint64_t msb = 0; + + auto uu = uuid.To128BitBE(); + for (int i = 0; i <= 7; i++) { + msb <<= 8; + msb |= uu[i]; + } + + return msb; +} + +static Uuid from_java_uuid(jlong uuid_msb, jlong uuid_lsb) { + std::array uu{}; + for (int i = 0; i < 8; i++) { + uu[7 - i] = (uuid_msb >> (8 * i)) & 0xFF; + uu[15 - i] = (uuid_lsb >> (8 * i)) & 0xFF; + } + return Uuid::From128BitBE(uu); +} + +static RawAddress str2addr(JNIEnv* env, jstring address) { + RawAddress bd_addr; + const char* c_address = env->GetStringUTFChars(address, NULL); + if (!c_address) return bd_addr; + + RawAddress::FromString(std::string(c_address), bd_addr); + env->ReleaseStringUTFChars(address, c_address); + + return bd_addr; +} + +namespace android { +static jmethodID method_onCsipAppRegistered; +static jmethodID method_onConnectionStateChanged; +static jmethodID method_onNewSetFound; +static jmethodID method_onNewSetMemberFound; +static jmethodID method_onLockStatusChanged; +static jmethodID method_onLockAvailable; +static jmethodID method_onSetSirkChanged; +static jmethodID method_onSetSizeChanged; + +static const btcsip_interface_t* sBluetoothCsipInterface = NULL; +static jobject mCallbacksObj = NULL; +static std::shared_timed_mutex mCallbacks_mutex; + +static jstring bdaddr2newjstr(JNIEnv* env, const RawAddress* bda) { + char c_address[32]; + snprintf(c_address, sizeof(c_address), "%02X:%02X:%02X:%02X:%02X:%02X", + bda->address[0], bda->address[1], bda->address[2], bda->address[3], + bda->address[4], bda->address[5]); + + return env->NewStringUTF(c_address); +} + +static void classInitNative(JNIEnv* env, jclass clazz) { + method_onCsipAppRegistered = + env->GetMethodID(clazz, "onCsipAppRegistered", "(IIJJ)V"); + + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "(ILjava/lang/String;II)V"); + + method_onNewSetFound = + env->GetMethodID(clazz, "onNewSetFound", "(ILjava/lang/String;I[BJJZ)V"); + + method_onNewSetMemberFound = + env->GetMethodID(clazz, "onNewSetMemberFound", "(ILjava/lang/String;)V"); + + method_onLockStatusChanged = + env->GetMethodID(clazz, "onLockStatusChanged", "(IIII[Ljava/lang/String;)V"); + + method_onLockAvailable = + env->GetMethodID(clazz, "onLockAvailable", "(IILjava/lang/String;)V"); + + method_onSetSirkChanged = + env->GetMethodID(clazz, "onSetSirkChanged", "(I[BLjava/lang/String;)V"); + + method_onSetSizeChanged = + env->GetMethodID(clazz, "onSetSizeChanged", "(IILjava/lang/String;)V"); + + ALOGI("%s: succeeds", __func__); +} + +static void csip_app_registered_callback(uint8_t status, uint8_t app_id, + const bluetooth::Uuid& uuid){ + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCsipAppRegistered, + status, app_id, UUID_PARAMS(uuid)); +} + +static void connection_state_changed_callback(uint8_t app_id, RawAddress& addr, + uint8_t state, uint8_t status){ + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &addr)); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, + app_id, address.get(), state, status); +} + +static void new_set_found_callback(uint8_t set_id, RawAddress& bd_addr, uint8_t size, + uint8_t* sirk, const bluetooth::Uuid& p_srvc_uuid, + bool lock_support) { + ALOGI("%s: ", __func__); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + + ALOGI("%s: new_set_found_callback: process sirk", __func__); + ScopedLocalRef jb(sCallbackEnv.get(), NULL); + jb.reset(sCallbackEnv->NewByteArray(16)); + sCallbackEnv->SetByteArrayRegion(jb.get(), 0, 16, (jbyte*)sirk); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNewSetFound, set_id, address.get(), + size, jb.get(), UUID_PARAMS(p_srvc_uuid), lock_support); +} + +static void new_set_member_found_cb(uint8_t set_id, RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNewSetMemberFound, set_id, address.get()); +} + +/** Callback for lock status changed event from stack + */ +static void lock_state_changed_callback(uint8_t app_id, uint8_t set_id, + uint8_t value, uint8_t status, + std::vector addr_list) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + + int i; + jstring bd_addr; + jobjectArray device_list; + jsize len = addr_list.size(); + device_list = sCallbackEnv->NewObjectArray( + len, sCallbackEnv->FindClass("java/lang/String"), 0); + + for(i = 0; i < len; i++) + { + bd_addr = sCallbackEnv->NewStringUTF(addr_list[i].ToString().c_str()); + sCallbackEnv->SetObjectArrayElement(device_list, i, bd_addr); + } + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onLockStatusChanged, + app_id, set_id, value, status, device_list); +} + +/** Callback when lock is available on earlier denying set member + */ +static void lock_available_callback(uint8_t app_id, uint8_t set_id, + RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + +// address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onLockAvailable, app_id, set_id, address.get()); +} + +/** Callback when size of coordinated set has been changed + */ +static void set_size_changed_callback(uint8_t set_id, uint8_t size, + RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetSizeChanged, set_id, size, address.get()); +} + +/** Callback when SIRK of coordinated set has been changed + */ +static void set_sirk_changed_callback(uint8_t set_id, uint8_t* sirk, + RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + + ScopedLocalRef jb(sCallbackEnv.get(), NULL); + jb.reset(sCallbackEnv->NewByteArray(24)); + sCallbackEnv->SetByteArrayRegion(jb.get(), 0, 24, (jbyte*)sirk); + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetSirkChanged, set_id, jb.get(), address.get()); +} + +static btcsip_callbacks_t sBluetoothCsipCallbacks = { + sizeof(sBluetoothCsipCallbacks), + csip_app_registered_callback, + connection_state_changed_callback, + new_set_found_callback, + new_set_member_found_cb, + lock_state_changed_callback, + lock_available_callback, + set_size_changed_callback, + set_sirk_changed_callback, +}; + +static void initNative(JNIEnv* env, jobject object) { + ALOGI("%s: initNative()", __func__); + + std::unique_lock lock(mCallbacks_mutex); + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == NULL) { + ALOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothCsipInterface != NULL) { + ALOGW("Cleaning up Bluetooth CSIP CLIENT Interface before initializing..."); + sBluetoothCsipInterface->cleanup(); + sBluetoothCsipInterface = NULL; + } + + if (mCallbacksObj != NULL) { + ALOGW("Cleaning up Bluetooth CSIP callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + sBluetoothCsipInterface = + (btcsip_interface_t*)btInf->get_profile_interface(BT_PROFILE_CSIP_CLIENT_ID); + if (sBluetoothCsipInterface == NULL) { + ALOGE("Failed to get Bluetooth CSIPInterface"); + return; + } + + bt_status_t status = sBluetoothCsipInterface->init(&sBluetoothCsipCallbacks); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed to initialize Bluetooth CSIP Client, status: %d", status); + sBluetoothCsipInterface = NULL; + return; + } + + mCallbacksObj = env->NewGlobalRef(object); +} + +static void cleanupNative(JNIEnv* env, jobject object) { + ALOGI("%s: cleanupNative()", __func__); + if (!sBluetoothCsipInterface) return; + + sBluetoothCsipInterface->cleanup(); +} + +static jboolean connectSetDeviceNative(JNIEnv* env, jobject object, + jint app_id, jbyteArray address) { + if (!sBluetoothCsipInterface) return JNI_FALSE; + + ALOGI("%s: connectSetDeviceNative()", __func__); + jboolean ret = JNI_TRUE; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + sBluetoothCsipInterface->connect(app_id, &bd_addr); + return ret; +} + +static jboolean disconnectSetDeviceNative(JNIEnv* env, jobject object, + jint app_id, jbyteArray address) { + if (!sBluetoothCsipInterface) return JNI_FALSE; + + jboolean ret = JNI_TRUE; + ALOGI("%s: disconnectSetDeviceNative()", __func__); + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + sBluetoothCsipInterface->disconnect(app_id, &bd_addr); + return ret; +} + +static void registerCsipAppNative(JNIEnv* env, jobject object, + jlong app_uuid_lsb, jlong app_uuid_msb) { + if (!sBluetoothCsipInterface) return; + + Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb); + sBluetoothCsipInterface->register_csip_app(uuid); +} + +static void unregisterCsipAppNative(JNIEnv* env, jobject object, jint app_id) { + if (!sBluetoothCsipInterface) return; + + sBluetoothCsipInterface->unregister_csip_app(app_id); +} + +static void setLockValueNative(JNIEnv* env, jobject object, jint app_id, + jint set_id, jint value, jobjectArray devicesList) { + if (!sBluetoothCsipInterface) return; + + std::vector lock_list; + int listCount = env->GetArrayLength(devicesList); + for (int i=0; i < listCount; i++) { + jstring address = (jstring) (env->GetObjectArrayElement(devicesList, i)); + RawAddress bd_addr = str2addr(env, address); + lock_list.push_back(bd_addr); + env->DeleteLocalRef(address); + } + + sBluetoothCsipInterface->set_lock_value(app_id, set_id, value, lock_list); +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectSetDeviceNative", "(I[B)Z", (void*)connectSetDeviceNative}, + {"disconnectSetDeviceNative", "(I[B)Z", (void*)disconnectSetDeviceNative}, + {"registerCsipAppNative", "(JJ)V", (void*)registerCsipAppNative}, + {"unregisterCsipAppNative", "(I)V", (void*)unregisterCsipAppNative}, + {"setLockValueNative", "(III[Ljava/lang/String;)V", (void*)setLockValueNative}, +}; + +int register_com_android_bluetooth_csip_client(JNIEnv* env) { + ALOGE("%s", __func__); + return jniRegisterNativeMethods( + env, "com/android/bluetooth/groupclient/GroupClientNativeInterface", + sMethods, NELEM(sMethods)); +} +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h new file mode 100644 index 0000000000000000000000000000000000000000..c82146d7ba819c71aa5640a5471c84d6277b5194 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#ifndef COM_ANDROID_BLUETOOTH_EXT +#define COM_ANDROID_BLUETOOTH_EXT + +namespace android { + +int register_com_android_bluetooth_bap_broadcast(JNIEnv* env); + +int register_com_android_bluetooth_acm(JNIEnv* env); + +int register_com_android_bluetooth_apm(JNIEnv* env); + +int register_com_android_bluetooth_csip_client(JNIEnv* env); + +int register_com_android_bluetooth_adv_audio_profiles(JNIEnv* env); + +int register_com_android_bluetooth_vcp_controller(JNIEnv* env); + +int register_com_android_bluetooth_pacs_client(JNIEnv* env); + +int register_com_android_bluetooth_mcp(JNIEnv* env); + +int register_com_android_bluetooth_call_controller(JNIEnv* env); +} + +#endif + diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5dc21227f4c97ca83068c82e8a637b9f063a2efb --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp @@ -0,0 +1,431 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#define LOG_TAG "BluetoothMCPService_jni" + +#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include + + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_mcp.h" +#include "hardware/bluetooth.h" + + + +using bluetooth::mcp_server::McpServerCallbacks; +using bluetooth::mcp_server::McpServerInterface; +using bluetooth::Uuid; +static McpServerInterface* sMcpServerInterface = nullptr; + + +namespace android { +static jmethodID method_OnConnectionStateChanged; +static jmethodID method_MediaControlPointChangedRequest; +static jmethodID method_TrackPositionChangedRequest; +static jmethodID method_PlayingOrderChangedRequest; + + +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class McpServerCallbacksImpl : public McpServerCallbacks { + public: + ~McpServerCallbacksImpl() = default; + + void OnConnectionStateChange(int state, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnConnectionStateChanged, + (jint)state, addr.get()); + } + + + void MediaControlPointChangeReq(uint8_t state, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_MediaControlPointChangedRequest, + (jint)state, addr.get()); + } + + void TrackPositionChangeReq(int32_t position) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_TrackPositionChangedRequest, + (jint)position); + } + + void PlayingOrderChangeReq(uint32_t order) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_PlayingOrderChangedRequest, + (jint)order); + } +}; + + +static McpServerCallbacksImpl sMcpServerCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + LOG(INFO) << __func__ << ": class init native"; + method_OnConnectionStateChanged = + env->GetMethodID(clazz, "OnConnectionStateChanged", "(I[B)V"); + LOG(INFO) << __func__ << ": class init native 1"; + method_MediaControlPointChangedRequest = + env->GetMethodID(clazz, "MediaControlPointChangedRequest", "(I[B)V"); + LOG(INFO) << __func__ << ": class init native 2"; + method_TrackPositionChangedRequest = + env->GetMethodID(clazz, "TrackPositionChangedRequest", "(I)V"); + method_PlayingOrderChangedRequest = + env->GetMethodID(clazz, "PlayingOrderChangedRequest", "(I)V"); + + LOG(INFO) << __func__ << ": succeeds"; +} + +// uuid not fixed +Uuid uuid = Uuid::FromString("00008fd1-0000-1000-8000-00805F9B34FB"); + +static void initNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sMcpServerInterface != nullptr) { + LOG(INFO) << "Cleaning up McpServer Interface before initializing..."; + sMcpServerInterface->Cleanup(); + sMcpServerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + LOG(INFO) << "Cleaning up McpServer callback object"; + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + LOG(ERROR) << "Failed to allocate Global Ref for Mcp Controller Callbacks"; + return; + } + LOG(INFO) << "mcs callback initialized"; + sMcpServerInterface = (McpServerInterface* )btInf->get_profile_interface( + BT_PROFILE_MCP_ID); + if (sMcpServerInterface == nullptr) { + LOG(ERROR) << "Failed to get Bluetooth Hearing Aid Interface"; + return; + } + + sMcpServerInterface->Init(&sMcpServerCallbacks, uuid); +} + +static void cleanupNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sMcpServerInterface != nullptr) { + sMcpServerInterface->Cleanup(); + sMcpServerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + + + +static jboolean mediaControlPointOpcodeSupportedNative(JNIEnv* env, jobject object, + jint feature) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->MediaControlPointOpcodeSupported(feature); + return JNI_TRUE; +} + +static jboolean mediaControlPointNative(JNIEnv* env, jobject object, + jint value) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->MediaControlPoint(value); + return JNI_TRUE; +} + +static jboolean mediaStateNative(JNIEnv* env, jobject object, + jint state) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->MediaState(state); + return JNI_TRUE; +} + +static jboolean mediaPlayerNameNative(JNIEnv* env, jobject object, + jstring playerName) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + + const char *nativeString = env->GetStringUTFChars(playerName, nullptr); + if (!nativeString) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + sMcpServerInterface->MediaPlayerName((uint8_t*)nativeString); + env->ReleaseStringUTFChars(playerName, nativeString); + return JNI_TRUE; +} + +static jboolean trackChangedNative(JNIEnv* env, jobject object, + jint status) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->TrackChanged((bool)status); + return JNI_TRUE; +} + +static jboolean trackPositionNative(JNIEnv* env, jobject object, + jint playPosition) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + + sMcpServerInterface->TrackPosition(playPosition); + return JNI_TRUE; +} + +static jboolean trackDurationNative(JNIEnv* env, jobject object, + jint duration) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->TrackDuration(duration); + + return JNI_TRUE; +} + + + +static jboolean trackTitleNative(JNIEnv* env, jobject object, + jstring title) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + const char *nativeString = env->GetStringUTFChars(title, nullptr); + if (!nativeString) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + sMcpServerInterface->TrackTitle((uint8_t*)nativeString); + env->ReleaseStringUTFChars(title, nativeString); + return JNI_TRUE; +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jint profile, jint set_id, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + if (bd_addr == RawAddress::kEmpty) { + LOG(INFO) << __func__ << " active device is null"; + } + + sMcpServerInterface->SetActiveDevice(bd_addr, set_id, profile); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean bondStateChangeNative(JNIEnv* env, jobject object, + jint state, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + + sMcpServerInterface->BondStateChange(*tmpraw, state); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean playingOrderSupportedNative(JNIEnv* env, jobject object, + jint order) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->PlayingOrderSupported(order); + + return JNI_TRUE; +} + +static jboolean playingOrderNative(JNIEnv* env, jobject object, + jint order) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->PlayingOrder(order); + + return JNI_TRUE; +} + +static jboolean contentControlIdNative(JNIEnv* env, jobject object, + jint ccid) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->ContentControlId(ccid); + return JNI_TRUE; +} + +static jboolean disconnectMcpNative(JNIEnv* env, jobject object, + jbyteArray address) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + + sMcpServerInterface->DisconnectMcp(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"mediaStateNative", "(I)Z", (void*)mediaStateNative}, + {"mediaPlayerNameNative", "(Ljava/lang/String;)Z", (void*)mediaPlayerNameNative}, + {"mediaControlPointOpcodeSupportedNative", "(I)Z", (void*)mediaControlPointOpcodeSupportedNative}, + {"mediaControlPointNative", "(I)Z", (void*)mediaControlPointNative}, + {"trackChangedNative", "(I)Z", (void*)trackChangedNative}, + {"trackTitleNative", "(Ljava/lang/String;)Z", (void*)trackTitleNative}, + {"trackPositionNative", "(I)Z", (void*)trackPositionNative}, + {"trackDurationNative", "(I)Z", (void*)trackDurationNative}, + {"playingOrderSupportedNative", "(I)Z", (void*)playingOrderSupportedNative}, + {"playingOrderNative", "(I)Z", (void*)playingOrderNative}, + {"setActiveDeviceNative", "(II[B)Z", (void*)setActiveDeviceNative}, + {"contentControlIdNative", "(I)Z", (void*)contentControlIdNative}, + {"disconnectMcpNative", "([B)Z", (void*)disconnectMcpNative}, + {"bondStateChangeNative", "(I[B)Z", (void*)bondStateChangeNative}, +}; + + + +int register_com_android_bluetooth_mcp(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/mcp/McpNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android + + diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c65a5d09d20c428c0dea7182c5a297c3b14177c0 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "BluetoothPacsClienServiceJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_pacs_client.h" + +#include +#include + +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::pacs::PacsClientCallbacks; + +namespace android { + +static jmethodID method_OnInitialized; +static jmethodID method_onConnectionStateChanged; +static jmethodID method_OnAudioContextAvailable; +static jmethodID method_onServiceDiscovery; + +static struct { + jclass clazz; + jmethodID constructor; + jmethodID getCodecType; + jmethodID getCodecPriority; + jmethodID getSampleRate; + jmethodID getBitsPerSample; + jmethodID getChannelMode; + jmethodID getCodecSpecific1; + jmethodID getCodecSpecific2; + jmethodID getCodecSpecific3; + jmethodID getCodecSpecific4; +} android_bluetooth_pacs_record; + +static PacsClientInterface* sPacsClientInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, + int client_id) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnInitialized, + (jint)status, (jint)client_id); + } + + void OnConnectionState(const RawAddress& bd_addr, + ConnectionState state) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, + addr.get(), (jint)state); + } + + void OnAudioContextAvailable(const RawAddress& bd_addr, + uint32_t available_contexts) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for available audio context"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnAudioContextAvailable, + addr.get(), (jint)available_contexts); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + + jsize i = 0; + jobjectArray sink_pac_records_array = sCallbackEnv->NewObjectArray( + (jsize)sink_pac_records.size(), + android_bluetooth_pacs_record.clazz, nullptr); + for (auto const& cap : sink_pac_records) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_pacs_record.clazz, + android_bluetooth_pacs_record.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(sink_pac_records_array, i++, capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + i = 0; + jobjectArray src_pac_records_array = sCallbackEnv->NewObjectArray( + (jsize)src_pac_records.size(), + android_bluetooth_pacs_record.clazz, nullptr); + for (auto const& cap : src_pac_records) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_pacs_record.clazz, + android_bluetooth_pacs_record.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(src_pac_records_array, i++, + capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&address); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceDiscovery, + sink_pac_records_array, src_pac_records_array, (jint)sink_locations, + (jint)src_locations, (jint)available_contexts, (jint)supported_contexts, + (jint)status, addr.get()); + } +}; + +static PacsClientCallbacksImpl sPacsClientCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + + jclass jniBluetoothCodecConfigClass = + env->FindClass("android/bluetooth/BluetoothCodecConfig"); + android_bluetooth_pacs_record.constructor = + env->GetMethodID(jniBluetoothCodecConfigClass, "", "(IIIIIJJJJ)V"); + android_bluetooth_pacs_record.getCodecType = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I"); + android_bluetooth_pacs_record.getCodecPriority = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I"); + android_bluetooth_pacs_record.getSampleRate = + env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I"); + android_bluetooth_pacs_record.getBitsPerSample = + env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I"); + android_bluetooth_pacs_record.getChannelMode = + env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I"); + android_bluetooth_pacs_record.getCodecSpecific1 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J"); + android_bluetooth_pacs_record.getCodecSpecific2 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J"); + android_bluetooth_pacs_record.getCodecSpecific3 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J"); + android_bluetooth_pacs_record.getCodecSpecific4 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J"); + + method_OnInitialized = + env->GetMethodID(clazz, "OnInitialized", "(II)V"); + + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V"); + + method_OnAudioContextAvailable = + env->GetMethodID(clazz, "OnAudioContextAvailable", "([BI)V"); + + method_onServiceDiscovery = + env->GetMethodID(clazz, "onServiceDiscovery", "([Landroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;" + "IIIII[B)V"); + + LOG(INFO) << __func__ << ": succeeds"; +} + +static void initNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sPacsClientInterface != nullptr) { + LOG(INFO) << "Cleaning up PacsClient Interface before initializing..."; + sPacsClientInterface->Cleanup(0); + sPacsClientInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + LOG(INFO) << "Cleaning up PacsClient callback object"; + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + LOG(ERROR) << "Failed to allocate Global Ref for pacs client Callbacks"; + return; + } + + android_bluetooth_pacs_record.clazz = (jclass)env->NewGlobalRef( + env->FindClass("android/bluetooth/BluetoothCodecConfig")); + if (android_bluetooth_pacs_record.clazz == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class", + __func__); + return; + } + + sPacsClientInterface = (PacsClientInterface*)btInf->get_profile_interface( + BT_PROFILE_PACS_CLIENT_ID); + if (sPacsClientInterface == nullptr) { + LOG(ERROR) << "Failed to get Bluetooth pacs client Interface"; + return; + } + + sPacsClientInterface->Init(&sPacsClientCallbacks); +} + +static void cleanupNative(JNIEnv* env, jobject object, jint client_id) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sPacsClientInterface != nullptr) { + sPacsClientInterface->Cleanup(client_id); + sPacsClientInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + env->DeleteGlobalRef(android_bluetooth_pacs_record.clazz); + android_bluetooth_pacs_record.clazz = nullptr; +} + +static jboolean connectPacsClientNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->Connect(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean disconnectPacsClientNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->Disconnect(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean startDiscoveryNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return JNI_FALSE; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->StartDiscovery(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static void GetAvailableAudioContextsNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->GetAvailableAudioContexts(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); +} + + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "(I)V", (void*)cleanupNative}, + {"connectPacsClientNative", "(I[B)Z", (void*)connectPacsClientNative}, + {"disconnectPacsClientNative", "(I[B)Z", (void*)disconnectPacsClientNative}, + {"startDiscoveryNative", "(I[B)Z", (void*)startDiscoveryNative}, + {"GetAvailableAudioContextsNative", "(I[B)Z", (void*)GetAvailableAudioContextsNative}, +}; + +int register_com_android_bluetooth_pacs_client(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/pc/PacsClientNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp new file mode 100644 index 0000000000000000000000000000000000000000..552fd8ad6be353a1002642e7dc56e1c0607ca727 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp @@ -0,0 +1,295 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "BluetoothVCPControllerJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_vcp_controller.h" + +#include +#include + +using bluetooth::vcp_controller::ConnectionState; +using bluetooth::vcp_controller::VcpControllerCallbacks; +using bluetooth::vcp_controller::VcpControllerInterface; + +namespace android { +static jmethodID method_onConnectionStateChanged; +static jmethodID method_onVolumeStateChange; +static jmethodID method_onVolumeFlagsChange; + +static VcpControllerInterface* sVcpControllerInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class VcpControllerCallbacksImpl : public VcpControllerCallbacks { + public: + ~VcpControllerCallbacksImpl() = default; + + void OnConnectionState(ConnectionState state, + const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, + (jint)state, addr.get()); + } + + + void OnVolumeStateChange(uint8_t volume, uint8_t mute, + const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeStateChange, + (jint)volume, (jboolean)mute, addr.get()); + } + + void OnVolumeFlagsChange(uint8_t flags, + const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeFlagsChange, + (jint)flags, addr.get()); + } +}; + +static VcpControllerCallbacksImpl sVcpControllerCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); + + method_onVolumeStateChange = + env->GetMethodID(clazz, "OnVolumeStateChange", "(II[B)V"); + + method_onVolumeFlagsChange = + env->GetMethodID(clazz, "OnVolumeFlagsChange", "(I[B)V"); + + LOG(INFO) << __func__ << ": succeeds"; +} + +static void initNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sVcpControllerInterface != nullptr) { + LOG(INFO) << "Cleaning up VcpController Interface before initializing..."; + sVcpControllerInterface->Cleanup(); + sVcpControllerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + LOG(INFO) << "Cleaning up VcpController callback object"; + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + LOG(ERROR) << "Failed to allocate Global Ref for Vcp Controller Callbacks"; + return; + } + + sVcpControllerInterface = (VcpControllerInterface*)btInf->get_profile_interface( + BT_PROFILE_VOLUME_CONTROL_ID); + if (sVcpControllerInterface == nullptr) { + LOG(ERROR) << "Failed to get Bluetooth Hearing Aid Interface"; + return; + } + + sVcpControllerInterface->Init(&sVcpControllerCallbacks); +} + +static void cleanupNative(JNIEnv* env, jobject object) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + LOG(ERROR) << "Bluetooth module is not loaded"; + return; + } + + if (sVcpControllerInterface != nullptr) { + sVcpControllerInterface->Cleanup(); + sVcpControllerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean connectVcpNative(JNIEnv* env, jobject object, + jbyteArray address, jboolean isDirect) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Connect(*tmpraw, isDirect); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean disconnectVcpNative(JNIEnv* env, jobject object, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Disconnect(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean setAbsVolumeNative(JNIEnv* env, jobject object, + jint volume, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->SetAbsVolume(volume, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean muteNative(JNIEnv* env, jobject object, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Mute(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean unmuteNative(JNIEnv* env, jobject object, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Unmute(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectVcpNative", "([BZ)Z", (void*)connectVcpNative}, + {"disconnectVcpNative", "([B)Z", (void*)disconnectVcpNative}, + {"setAbsVolumeNative", "(I[B)Z", (void*)setAbsVolumeNative}, + {"muteNative", "([B)Z", (void*)muteNative}, + {"unmuteNative", "([B)Z", (void*)unmuteNative}, +}; + +int register_com_android_bluetooth_vcp_controller(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/vcp/VcpControllerNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6c79c663f0e0490a24a507620264725a2d4e6f80 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java @@ -0,0 +1,141 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.acm; + +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.os.SystemProperties; +import android.util.Log; +import com.android.bluetooth.R; +import com.android.bluetooth.btservice.AdapterService; + +import java.util.Arrays; +import java.util.Objects; +/* + * ACM Codec Configuration setup. + */ +class AcmCodecConfig { + private static final boolean DBG = true; + private static final String TAG = "AcmCodecConfig"; + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + + private Context mContext; + private AcmNativeInterface mAcmNativeInterface; + + private BluetoothCodecConfig[] mCodecConfigPriorities; + private int mAcmSourceCodecPriorityLC3 = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; + + private int assigned_codec_length = 0; + AcmCodecConfig(Context context, AcmNativeInterface acmNativeInterface) { + mContext = context; + mAcmNativeInterface = acmNativeInterface; + mCodecConfigPriorities = assignCodecConfigPriorities(); + } + + BluetoothCodecConfig[] codecConfigPriorities() { + return mCodecConfigPriorities; + } + + void setCodecConfigPreference(BluetoothDevice device, + BluetoothCodecConfig newCodecConfig, + int contextType) { + //Objects.requireNonNull(codecStatus); + + /*// Check whether the codecConfig is selectable for this Bluetooth device. + BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities(); + if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec -> + codec.isMandatoryCodec())) { + // Do not set codec preference to native if the selectableCodecs not contain mandatory + // codec. The reason could be remote codec negotiation is not completed yet. + Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing."); + return; + } + if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) { + Log.w(TAG, "setCodecConfigPreference: invalid codec " + + Objects.toString(newCodecConfig)); + return; + } + + // Check whether the codecConfig would change current codec config. + int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs); + BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig(); + if (prioritizedCodecType == currentCodecConfig.getCodecType() + && (prioritizedCodecType != newCodecConfig.getCodecType() + || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig) + && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) { + // Same codec with same parameters, no need to send this request to native. + Log.w(TAG, "setCodecConfigPreference: codec not changed."); + return; + }*/ + + BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1]; + codecConfigArray[0] = newCodecConfig; + mAcmNativeInterface.setCodecConfigPreference(device, codecConfigArray, contextType, contextType); + } + + // Get the codec type of the highest priority of selectableCodecs and codecConfig. + private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig, + BluetoothCodecConfig[] selectableCodecs) { + BluetoothCodecConfig prioritizedCodecConfig = codecConfig; + for (BluetoothCodecConfig config : selectableCodecs) { + if (prioritizedCodecConfig == null) { + prioritizedCodecConfig = config; + } + if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) { + prioritizedCodecConfig = config; + } + } + return prioritizedCodecConfig.getCodecType(); + } + + // Assign the ACM Source codec config priorities + private BluetoothCodecConfig[] assignCodecConfigPriorities() { + Resources resources = mContext.getResources(); + if (resources == null) { + return null; + } + + int value; + mAcmSourceCodecPriorityLC3 = BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST; + + BluetoothCodecConfig codecConfig; + BluetoothCodecConfig[] codecConfigArray; + int codecCount = 0; + codecConfigArray = + new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_QVA_CODEC_TYPE_MAX]; + + codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + mAcmSourceCodecPriorityLC3, BluetoothCodecConfig.SAMPLE_RATE_NONE, + BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig + .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, + 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); + codecConfigArray[codecCount++] = codecConfig; + assigned_codec_length = codecCount; + return codecConfigArray; + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..a7f24dcc6e8ab9ba3fd8363083e5c3195f134f45 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java @@ -0,0 +1,238 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.acm; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.util.Log; +import java.util.List; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * ACM Native Interface to/from JNI. + */ +public class AcmNativeInterface { + private static final String TAG = "AcmNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + @GuardedBy("INSTANCE_LOCK") + private static AcmNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + @VisibleForTesting + private AcmNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtf(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static AcmNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new AcmNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * @param maxConnectedAudioDevices maximum number of A2DP Sink devices that can be connected + * simultaneously + * @param codecConfigPriorities an array with the codec configuration + * priorities to configure. + */ + public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities) { + initNative(maxConnectedAudioDevices, codecConfigPriorities); + } + + + /** + * Initiates ACM connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean connectAcm(BluetoothDevice device, int contextType, int profileType, int preferredContext) { + return connectAcmNative(getByteAddress(device), contextType, profileType, preferredContext); + } + + /** + * Disconnects ACM from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean disconnectAcm(BluetoothDevice device, int contextType) { + return disconnectAcmNative(getByteAddress(device), contextType); + } + + /** + * Sets a connected ACM group/remote as active. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean setActiveDevice(BluetoothDevice device, int contextType) { + return setActiveDeviceNative(getByteAddress(device), contextType); + } + + /** + * Sends Start stream to remote group/remote for voice call. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean startStream(BluetoothDevice device, int contextType) { + return startStreamNative(getByteAddress(device), contextType); + } + + /** + * Sends Stop stream to remote group/remote for voice call. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean stopStream(BluetoothDevice device, int contextType) { + return stopStreamNative(getByteAddress(device), contextType); + } + + /** + * Sets the codec configuration preferences. + * + * @param device the remote Bluetooth device + * @param codecConfigArray an array with the codec configurations to + * configure. + * @return true on success, otherwise false. + */ + public boolean setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig[] codecConfigArray, + int contextType, int preferredContext) { + return setCodecConfigPreferenceNative(getByteAddress(device), codecConfigArray, + contextType, preferredContext); + } + + public boolean ChangeCodecConfigPreference(BluetoothDevice device, + String message) { + return ChangeCodecConfigPreferenceNative(getByteAddress(device), message); + } + /** + * Cleanup the native interface. + */ + public void cleanup() { + cleanupNative(); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + private void sendMessageToService(AcmStackEvent event) { + AcmService service = AcmService.getAcmService(); + if (service != null) { + service.messageFromNative(event); + } else { + Log.w(TAG, "Event ignored, service not available: " + event); + } + } + + // Callbacks from the native stack back into the Java framework. + // All callbacks are routed via the Service which will disambiguate which + // state machine the message should be routed to. + + private void onConnectionStateChanged(byte[] address, int state, int contextType) { + AcmStackEvent event = + new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + event.valueInt2 = contextType; + + if (DBG) { + Log.d(TAG, "onConnectionStateChanged: " + event); + } + sendMessageToService(event); + } + + private void onAudioStateChanged(byte[] address, int state, int contextType) { + AcmStackEvent event = new AcmStackEvent(AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + event.valueInt2 = contextType; + + if (DBG) { + Log.d(TAG, "onAudioStateChanged: " + event); + } + sendMessageToService(event); + } + + private void onCodecConfigChanged(byte[] address, + BluetoothCodecConfig newCodecConfig, + BluetoothCodecConfig[] codecsLocalCapabilities, + BluetoothCodecConfig[] codecsSelectableCapabilities, int contextType) { + AcmStackEvent event = new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); + event.device = getDevice(address); + event.codecStatus = new BluetoothCodecStatus(newCodecConfig, + codecsLocalCapabilities, + codecsSelectableCapabilities); + event.valueInt2 = contextType; + if (DBG) { + Log.d(TAG, "onCodecConfigChanged: " + event); + } + sendMessageToService(event); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(int maxConnectedAudioDevices, + BluetoothCodecConfig[] codecConfigPriorities); + private native boolean connectAcmNative(byte[] address, int contextType, int profileType, int preferredContext); + private native boolean disconnectAcmNative(byte[] address, int contextType); + private native boolean setActiveDeviceNative(byte[] address, int contextType); + private native boolean startStreamNative(byte[] address, int contextType); + private native boolean stopStreamNative(byte[] address, int contextType); + private native boolean setCodecConfigPreferenceNative(byte[] address, + BluetoothCodecConfig[] codecConfigArray, int contextType, int preferredContext); + private native boolean ChangeCodecConfigPreferenceNative(byte[] address, String Id); + private native void cleanupNative(); +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java new file mode 100644 index 0000000000000000000000000000000000000000..ef264a11b8b1ba671c090c65386bebcd9d63b6e1 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java @@ -0,0 +1,1866 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.acm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus; +import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.HandlerThread; +import android.os.Handler; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelUuid; +import android.util.Log; +import android.os.Message; +import android.bluetooth.BluetoothGroupCallback; +import com.android.bluetooth.groupclient.GroupService; +import android.bluetooth.DeviceGroup; +import android.bluetooth.BluetoothDeviceGroup; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.apm.VolumeManager; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import android.os.SystemProperties; +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.BluetoothStatsLog; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothAdapter; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import com.android.bluetooth.vcp.VcpController; +/** + * Provides Bluetooth ACM profile, as a service in the Bluetooth application. + * @hide + */ +public class AcmService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = "AcmService"; + private String mAcmName; + public static final int ACM_AUDIO_UNICAST = 25; + public static final int INVALID_SET_ID = 0x10; + private static AcmService sAcmService; + private BluetoothAdapter mAdapter; + private AdapterService mAdapterService; + private HandlerThread mStateMachinesThread; + private static final int LOCK_RELEASED = 0; // (LOCK Released successfully) + private static final int LOCK_RELEASED_TIMEOUT = 1; // (LOCK Released by timeout) + private static final int ALL_LOCKS_ACQUIRED = 2; // (LOCK Acquired for all requested set members) + private static final int SOME_LOCKS_ACQUIRED_REASON_TIMEOUT = 3; // (Request timeout for some set members) + private static final int SOME_LOCKS_ACQUIRED_REASON_DISC = 4; // (Some of the set members were disconnected) + private static final int LOCK_DENIED = 5; // (Denied by one of the set members) + private static final int INVALID_REQUEST_PARAMS = 6; // (Upper layer provided invalid parameters) + private static final int LOCK_RELEASE_NOT_ALLOWED = 7; // (Response from remote (PTS)) + private static final int INVALID_VALUE = 8; + @VisibleForTesting + AcmNativeInterface mAcmNativeInterface; + @VisibleForTesting + ServiceFactory mFactory = new ServiceFactory(); + + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + static final int CONTEXT_TYPE_BROADCAST_AUDIO = 6; + + private AcmCodecConfig mAcmCodecConfig; + private final Object mAudioManagerLock = new Object(); + private final Object mBtLeaLock = new Object(); + private final Object mBtAcmLock = new Object(); + private String mLeaChannelMode = "stereo"; + private AudioManager mAudioManager; + @GuardedBy("mStateMachines") + private BluetoothDevice mGroupBdAddress = null; + private BluetoothDevice mActiveDevice = null; + private BluetoothDevice mActiveDeviceVoice = null; + private int mActiveDeviceProfile = 0; + private int mActiveDeviceVoiceProfile = 0; + private final ConcurrentMap mStateMachines = + new ConcurrentHashMap<>(); + private HashMap mAcmDevices = + new HashMap(); + + // Upper limit of all ACM devices: Bonded or Connected + private static final int MAX_ACM_STATE_MACHINES = 50; + // Upper limit of all ACM devices that are Connected or Connecting + private int mMaxConnectedAudioDevices = 1; + CsipManager mCsipManager = null; + boolean mIsCsipRegistered = false; + boolean mShoPend = false; + boolean mVoiceShoPend = false; + //volume + private int mAudioStreamMax; + private int mActiveDeviceLocalMediaVol; + private int mActiveDeviceLocalVoiceVol; + private boolean mActiveDeviceIsMuted; + private static final int VCP_MAX_VOL = 255; + private VcpController mVcpController; + + private BroadcastReceiver mBondStateChangedReceiver; + private final ReentrantReadWriteLock mAcmNativeInterfaceLock = new ReentrantReadWriteLock(); + public int mCsipAppId = -1; + + private static final int SET_EBMONO_CFG = 1; + private static final int SET_EBSTEREO_CFG = 2; + private static final int MonoCfg_Timeout = 3000; + private static final int StereoCfg_Timeout = 3000; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) + { + synchronized(mBtLeaLock) { + switch (msg.what) { + case SET_EBMONO_CFG: + Log.d(TAG, "setparameters to Mono"); + synchronized (mAudioManagerLock) { + if(mAudioManager != null) + mAudioManager.setParameters("LEAMono=true"); + } + mLeaChannelMode = "mono"; + break; + case SET_EBSTEREO_CFG: + Log.d(TAG, "setparameters to stereo"); + synchronized (mAudioManagerLock) { + if(mAudioManager != null) + mAudioManager.setParameters("LEAMono=false"); + } + mLeaChannelMode = "stereo"; + break; + default: + break; + } + } + } + }; + + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + String propValue; + + if (sAcmService != null) { + Log.w(TAG, "AcmService is already running"); + return true; + } + + // Step 1: Get AdapterService, AcmNativeInterface. + // None of them can be null. + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when AcmService starts"); + mAcmNativeInterface = Objects.requireNonNull(AcmNativeInterface.getInstance(), + "AcmNativeInterface cannot be null when AcmService starts"); + + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when StreamAudioService starts"); + + Log.i(TAG, "mAdapterService.isHostAdvAudioUnicastFeatureSupported() returned " + + mAdapterService.isHostAdvAudioUnicastFeatureSupported()); + Log.i(TAG, "mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported() returned " + + mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported()); + Log.i(TAG, "mAdapterService.isAdvUnicastAudioFeatEnabled() returned " + + mAdapterService.isAdvUnicastAudioFeatEnabled()); + + // SOC supports unicast, host supports unicast and stereo recording + if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && + mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported() && + mAdapterService.isAdvUnicastAudioFeatEnabled()) { + + Log.i(TAG, "SOC supports unicast, host supports unicast, stereo recording"); + // set properties only if they are not set to allow user enable/disable + // the features explicitly + propValue = SystemProperties.get("persist.vendor.service.bt.bap.enable_ucast"); + + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.service.bt.bap.enable_ucast", "true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.bap.enable_ucast is already set to " + + propValue); + } + + propValue = SystemProperties.get("persist.vendor.service.bt.recording_supported"); + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.service.bt.recording_supported", "true"); + Log.i(TAG, "persist.vendor.service.bt.recording_supported set to true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.recording_supported is already set to " + + propValue); + } + } + + Log.i(TAG, "mAdapterService.isHostQHSFeatureSupported() returned " + + mAdapterService.isHostQHSFeatureSupported()); + + // SOC supports unicast, host supports unicast and QHS + if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && + mAdapterService.isHostQHSFeatureSupported() && + mAdapterService.isAdvUnicastAudioFeatEnabled()) { + + Log.i(TAG, "SOC supports unicast, host supports unicast, QHS"); + // set properties only if they are not set to allow user enable/disable + // the features explicitly + propValue = SystemProperties.get("persist.vendor.btstack.qhs_enable"); + + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.btstack.qhs_enable", "true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.bap.enable_ucast is already set to " + + propValue); + } + } + + Log.i(TAG, "isHostAdvAudioLC3QFeatureSupported(): " + + mAdapterService.isHostAdvAudioLC3QFeatureSupported()); + + // SOC supports unicast, host supports unicast and LC3Q + if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && + mAdapterService.isHostAdvAudioLC3QFeatureSupported() && + mAdapterService.isAdvUnicastAudioFeatEnabled()) { + + Log.i(TAG, "host supports LC3Q"); + // set properties only if they are not set to allow user enable/disable + // the features explicitly + propValue = SystemProperties.get("persist.vendor.service.bt.is_lc3q_supported"); + + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.service.bt.is_lc3q_supported", "true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.is_lc3q_supported is already set to " + + propValue); + } + } + + // Step 2: Get maximum number of connected audio devices + mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); + Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); + + + String LeaChannelMode = SystemProperties.get("persist.vendor.btstack.Lea.defaultchannelmode"); + if (!LeaChannelMode.isEmpty() && "mono".equals(LeaChannelMode)) { + mLeaChannelMode = "mono"; + } + Log.d(TAG, "Default LEA ChannelMode: " + LeaChannelMode); + // Step 3: Start handler thread for state machines + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("AcmService.StateMachines"); + mStateMachinesThread.start(); + + // Step 4: Setup codec config + mAcmCodecConfig = new AcmCodecConfig(this, mAcmNativeInterface); + + if (mAdapterService.isAdvUnicastAudioFeatEnabled()) { + Log.d(TAG, "Initialize AcmNativeInterface"); + // Step 5: Initialize native interface + mAcmNativeInterface.init(mMaxConnectedAudioDevices, + mAcmCodecConfig.codecConfigPriorities()); + } + + // Step 6: Setup broadcast receivers + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + registerReceiver(mBondStateChangedReceiver, filter); + synchronized (mAudioManagerLock) { + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when AcmService starts"); + mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + } + // Step 7: Mark service as started + setAcmService(this); + + //step 8: Register CSIP module + mCsipManager = new CsipManager(); + + //step 9: Get Vcp Controller + mVcpController = VcpController.make(this); + Objects.requireNonNull(mVcpController, "mVcpController cannot be null when AcmService starts"); + return true; + } + + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sAcmService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + + // Step 9: do quit Vcp Controller + if (mVcpController != null) { + mVcpController.doQuit(); + } + + // Step 8: Mark service as stopped + setAcmService(null); + + unregisterReceiver(mBondStateChangedReceiver); + mBondStateChangedReceiver = null; + // Step 6: Cleanup native interface + mAcmNativeInterface.cleanup(); + mAcmNativeInterface = null; + + // Step 5: Clear codec config + mAcmCodecConfig = null; + + // Step 4: Destroy state machines and stop handler thread + synchronized (mStateMachines) { + for (AcmStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + + // Step 2: Reset maximum number of connected audio devices + mMaxConnectedAudioDevices = 1; + + // Step 1: Clear AdapterService, AcmNativeInterface, AudioManager + mAcmNativeInterface = null; + mAdapterService = null; + if (mAcmDevices != null) + mAcmDevices.clear(); + + mCsipManager.unregisterCsip(); + mCsipManager = null; + return true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + } + + @Override + protected IProfileServiceBinder initBinder() { + return new AcmBinder(this); + } + + private class BluetoothAcmDevice { + private BluetoothDevice mGrpDevice; // group bd address + private int mState; + private int msetID; + + BluetoothAcmDevice(BluetoothDevice device, int state, int setID) { + mGrpDevice = device; + mState = state; + msetID = setID; + } + } + + private BluetoothDevice getAddressFromString(String address) { + return mAdapter.getRemoteDevice(address); + } + + public BluetoothDevice makeGroupBdAddress(BluetoothDevice device, int state, int setid) { + Log.i(TAG, " Set id : " + setid + " Num of connected acm devices: " + mAcmDevices.size()); + boolean setIdMatched = false; + if (setid == INVALID_SET_ID) { + Log.d(TAG, "Device is not part of any group"); + BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(device, state, setid); + mAcmDevices.put(device, acmDevice); + mGroupBdAddress = acmDevice.mGrpDevice; + return mGroupBdAddress; + } + // BluetoothDevice bdaddr = null; + if (mAcmDevices == null) { + Log.d(TAG, "Hash Map is NULL"); + return mGroupBdAddress; + } + if (mAcmDevices.containsKey(device)) { + Log.d(TAG, "Device is available in Hash Map"); + BluetoothAcmDevice acmDevice = mAcmDevices.get(device); + mGroupBdAddress = acmDevice.mGrpDevice; + return mGroupBdAddress; + } + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (d.msetID == setid) { + setIdMatched = true; + Log.d(TAG, "Device is part of same set ID"); + BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(d.mGrpDevice, state, setid); + mAcmDevices.put(device, acmDevice); + mGroupBdAddress = acmDevice.mGrpDevice; + break; + } + } + } + if (!setIdMatched) { + Log.d(TAG, "create new group or device is not part of existing set ID"); + String address = "9E:8B:00:00:00:0"; + BluetoothDevice bdaddr = getAddressFromString(address + setid); + BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(bdaddr, state, setid); + mAcmDevices.put(device, acmDevice); + mGroupBdAddress = bdaddr; + } + return mGroupBdAddress; + } + + public void handleAcmDeviceStateChange(BluetoothDevice device, int state, int setid) { + Log.d(TAG, "handleAcmDeviceStateChange: device: " + device + ", state: " + state + + " Set id : " + setid); + Log.i(TAG, " Num of connected ACM devices: " + mAcmDevices.size()); + boolean update = false; + if (device == null || mAcmDevices.size() == 0) + return; + BluetoothAcmDevice acmDevice = mAcmDevices.get(device); + //check if current active group address is same as this device group address + if (acmDevice != null && mGroupBdAddress != acmDevice.mGrpDevice) { + Log.d(TAG, "Inactive device is disconnected"); + update = true; + } + if (state == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "Remove Device from hash map"); + mAcmDevices.remove(device); + } else { + acmDevice.mState = state; + Log.d(TAG, "Update state"); + } + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (d.msetID == setid) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + update = true; + Log.d(TAG, "Atleast one member is connected"); + break; + } + } + } + if (!update) { + /*if (!mAcmNativeInterface.setActiveDevice(null, 0)) {//send unknown context type + Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native layer"); + }*/ + } + } + + public static synchronized AcmService getAcmService() { + if (sAcmService == null) { + Log.w(TAG, "getAcmService(): service is null"); + return null; + } + if (!sAcmService.isAvailable()) { + Log.w(TAG, "getAcmService(): service is not available"); + return null; + } + return sAcmService; + } + + private static synchronized void setAcmService(AcmService instance) { + if (DBG) { + Log.d(TAG, "setAcmService(): set to: " + instance); + } + sAcmService = instance; + } + + public boolean connect(BluetoothDevice device, int contextType, + int profileType, int preferredContext) { + + if (DBG) { + Log.d(TAG, "connect(): " + device + " contextType: " + contextType + + " profileType: " + profileType + " preferredContext: " + preferredContext); + } + if (device.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "Connect request for group"); + byte[] addrByte = Utils.getByteAddress(device); + int set_id = addrByte[5]; + List d = mCsipManager.getSetMembers(set_id); + if (d == null) { + Log.d(TAG, "No set member found"); + return false; + } + Iterator members = d.iterator(); + if (members != null) { + while (members.hasNext()) { + BluetoothDevice addr = members.next(); + Log.d(TAG, "connect member: " + addr); + synchronized (mStateMachines) { + if (!connectionAllowedCheckMaxDevices(addr)) { + // when mMaxConnectedAudioDevices is one, disconnect current device first. + if (mMaxConnectedAudioDevices == 1) { + List sinks = getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + for (BluetoothDevice sink : sinks) { + if (sink.equals(addr)) { + Log.w(TAG, "Connecting to device " + addr + " : disconnect skipped"); + continue; + } + disconnect(sink, contextType); + } + } else { + Log.e(TAG, "Cannot connect to " + addr + " : too many connected devices"); + return false; + } + } + AcmStateMachine smConnect = getOrCreateStateMachine(addr); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + addr + " : no state machine"); + return false; + } + Message msg = smConnect.obtainMessage(AcmStateMachine.CONNECT); + msg.obj = preferredContext; + msg.arg1 = contextType; + msg.arg2 = profileType; + smConnect.sendMessage(msg); + } + } + } + return true; + } + synchronized (mStateMachines) { + if (!connectionAllowedCheckMaxDevices(device)) { + // when mMaxConnectedAudioDevices is one, disconnect current device first. + if (mMaxConnectedAudioDevices == 1) { + List sinks = getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + for (BluetoothDevice sink : sinks) { + if (sink.equals(device)) { + Log.w(TAG, "Connecting to device " + device + " : disconnect skipped"); + continue; + } + disconnect(sink, contextType); + } + } else { + Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); + return false; + } + } + AcmStateMachine smConnect = getOrCreateStateMachine(device); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + device + " : no state machine"); + return false; + } + Message msg = smConnect.obtainMessage(AcmStateMachine.CONNECT); + msg.obj = preferredContext; + msg.arg1 = contextType; + msg.arg2 = profileType; + smConnect.sendMessage(msg); + return true; + } + } + + /** + * Disconnects Acm for the remote bluetooth device + * + * @param device is the device with which we would like to disconnect acm + * @return true if profile disconnected, false if device not connected over acm + */ + public boolean disconnect(BluetoothDevice device, int contextType) { + + if (DBG) { + Log.d(TAG, "disconnect(): " + device); + } + + if (device.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "Disonnect request for group"); + byte[] addrByte = Utils.getByteAddress(device); + int set_id = addrByte[5]; + List d = mCsipManager.getSetMembers(set_id); + if (d == null) { + Log.d(TAG, "No set member found"); + return false; + } + Iterator members = d.iterator(); + if (members != null) { + while (members.hasNext()) { + BluetoothDevice addr = members.next(); + Log.d(TAG, "disconnect member: " + device); + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(addr); + if (sm == null) { + Log.e(TAG, "Ignored disconnect request for " + addr + " : no state machine"); + return false; + } + Message msg = sm.obtainMessage(AcmStateMachine.DISCONNECT); + msg.obj = contextType; + sm.sendMessage(msg); + } + } + return true; + } + } + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine"); + return false; + } + Message msg = sm.obtainMessage(AcmStateMachine.DISCONNECT); + msg.obj = contextType; + sm.sendMessage(msg); + return true; + } + } + + public List getConnectedDevices() { + + synchronized (mStateMachines) { + List devices = new ArrayList<>(); + for (AcmStateMachine sm : mStateMachines.values()) { + if (sm.isConnected()) { + devices.add(sm.getDevice()); + } + } + return devices; + } + } + + //check if it can be a list ? + public BluetoothDevice getCsipLockRequestedDevice() { + + synchronized (mStateMachines) { + BluetoothDevice device = null; + for (AcmStateMachine sm : mStateMachines.values()) { + if (sm.isCsipLockRequested()) { + device = sm.getDevice(); + } + } + return device; + } + } + + public boolean IsLockSupportAvailable(BluetoothDevice device) { + boolean isLockSupported = false; + /*int setId = mSetCoordinator.getRemoteSetId(device, ACM_UUID); + DeviceGroup set = mSetCoordinator.getDeviceGroup(setId); + isLockSupported = set.mLockSupport;*/ + //isLockSupported = mAdapterService.isCsipLockSupport(device); + Log.d(TAG, "Exclusive Access SupportAvaible for:" + device + "returns " + isLockSupported); + return isLockSupported; + } + + /** + * Check whether can connect to a peer device. + * The check considers the maximum number of connected peers. + * + * @param device the peer device to connect to + * @return true if connection is allowed, otherwise false + */ + private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) { + int connected = 0; + // Count devices that are in the process of connecting or already connected + synchronized (mStateMachines) { + for (AcmStateMachine sm : mStateMachines.values()) { + switch (sm.getConnectionState()) { + case BluetoothProfile.STATE_CONNECTING: + case BluetoothProfile.STATE_CONNECTED: + if (Objects.equals(device, sm.getDevice())) { + return true; // Already connected or accounted for + } + connected++; + break; + default: + break; + } + } + } + return (connected < mMaxConnectedAudioDevices); + } + + List getDevicesMatchingConnectionStates(int[] states) { + + List devices = new ArrayList<>(); + if (states == null) { + return devices; + } + final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); + if (bondedDevices == null) { + return devices; + } + synchronized (mStateMachines) { + for (BluetoothDevice device : bondedDevices) { + /*if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device), + BluetoothUuid.ACM_SINK)) { + continue; + }*/ + int connectionState = BluetoothProfile.STATE_DISCONNECTED; + AcmStateMachine sm = mStateMachines.get(device); + if (sm != null) { + connectionState = sm.getConnectionState(); + } + for (int state : states) { + if (connectionState == state) { + devices.add(device); + break; + } + } + } + return devices; + } + } + + /** + * Get the list of devices that have state machines. + * + * @return the list of devices that have state machines + */ + @VisibleForTesting + List getDevices() { + List devices = new ArrayList<>(); + synchronized (mStateMachines) { + for (AcmStateMachine sm : mStateMachines.values()) { + devices.add(sm.getDevice()); + } + return devices; + } + } + + public int getConnectionState(BluetoothDevice device) { + + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + public int getCsipConnectionState(BluetoothDevice device) { + + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getCsipConnectionState(); + } + } + + // Handle messages from native (JNI) to Java + void messageFromNative(AcmStackEvent stackEvent) { + Objects.requireNonNull(stackEvent.device, + "Device should never be null, event: " + stackEvent); + synchronized (mStateMachines) { + BluetoothDevice device = stackEvent.device; + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + if (stackEvent.type == AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { + switch (stackEvent.valueInt1) { + case AcmStackEvent.CONNECTION_STATE_CONNECTED: + case AcmStackEvent.CONNECTION_STATE_CONNECTING: + // Create a new state machine only when connecting to a device + if (!connectionAllowedCheckMaxDevices(device)) { + Log.e(TAG, "Cannot connect to " + device + + " : too many connected devices"); + return; + } + sm = getOrCreateStateMachine(device); + break; + default: + break; + } + } + } + if (sm == null) { + Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); + return; + } + sm.sendMessage(AcmStateMachine.STACK_EVENT, stackEvent); + } + } + + private AcmStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + // Limit the maximum number of state machines to avoid DoS attack + if (mStateMachines.size() >= MAX_ACM_STATE_MACHINES) { + Log.e(TAG, "Maximum number of ACM state machines reached: " + + MAX_ACM_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = AcmStateMachine.make(device, this, mAcmNativeInterface, + mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + return; + } + } + removeStateMachine(device); + } + + private void removeStateMachine(BluetoothDevice device) { + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.w(TAG, "removeStateMachine: device " + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.doQuit(); + sm.cleanup(); + mStateMachines.remove(device); + mAcmDevices.remove(device); + } + } + + void updateLeaChannelMode(int state, BluetoothDevice device) { + BluetoothDevice peerLeaDevice = null; + peerLeaDevice = getLeaPeerDevice(device); + if (peerLeaDevice == null) { + Log.d(TAG, "updateLeaChannelMode: peer device is NULL"); + return; + } + Log.d(TAG, "LeaChannelMode: " + mLeaChannelMode + "state: " + state); + synchronized(mBtLeaLock) { + if ("mono".equals(mLeaChannelMode)) { + if ((state == BluetoothA2dp.STATE_PLAYING) && (peerLeaDevice!= null) + && peerLeaDevice.isConnected() && isAcmPlayingMusic(peerLeaDevice)) { + Log.d(TAG, "updateLeaChannelMode: send delay message to set stereo "); + Message msg = mHandler.obtainMessage(SET_EBSTEREO_CFG); + mHandler.sendMessageDelayed(msg, StereoCfg_Timeout); + } else if (state == BluetoothA2dp.STATE_PLAYING) { + Log.d(TAG, "updateLeaChannelMode: setparameters to Mono"); + synchronized (mAudioManagerLock) { + if (mAudioManager != null) { + Log.d(TAG, "updateLeaChannelMode: Acquired mVariableLock"); + mAudioManager.setParameters("LeaChannelConfig=mono"); + } + } + Log.d(TAG, "updateLeaChannelMode: Released mVariableLock"); + } + if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && + isAcmPlayingMusic(peerLeaDevice)) { + if (mHandler.hasMessages(StereoCfg_Timeout)) { + Log.d(TAG, "updateLeaChannelMode:remove delay message for stereo"); + mHandler.removeMessages(StereoCfg_Timeout); + } + } + } else if ("stereo".equals(mLeaChannelMode)) { + if ((state == BluetoothA2dp.STATE_PLAYING) && + (getConnectionState(peerLeaDevice) != BluetoothProfile.STATE_CONNECTED + || !isAcmPlayingMusic(peerLeaDevice))) { + Log.d(TAG, "updateLeaChannelMode: send delay message to set mono"); + Message msg = mHandler.obtainMessage(SET_EBMONO_CFG); + mHandler.sendMessageDelayed(msg, MonoCfg_Timeout); + } + if ((state == BluetoothA2dp.STATE_PLAYING) && isAcmPlayingMusic(peerLeaDevice)) { + if (mHandler.hasMessages(SET_EBMONO_CFG)) { + Log.d(TAG, "updateLeaChannelMode: remove delay message to set mono"); + mHandler.removeMessages(SET_EBMONO_CFG); + } + } + if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && isAcmPlayingMusic(peerLeaDevice)) { + Log.d(TAG, "setparameters to Mono"); + synchronized (mAudioManagerLock) { + if (mAudioManager != null) + mAudioManager.setParameters("LeaChannelConfig=mono"); + } + mLeaChannelMode = "mono"; + } + } + } + } + + private BluetoothDevice getLeaPeerDevice(BluetoothDevice device) { + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return null; + } + return sm.getPeerDevice(); + } + } + + public boolean isPeerDeviceConnected(BluetoothDevice device, int setid) { + boolean isConnected = false; + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if ((d.msetID == setid) && !Objects.equals(dm, device)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + isConnected = true; + Log.d(TAG, "At least one member is in connected state"); + break; + } + } + } + } + return isConnected; + } + + public boolean isPeerDeviceStreamingMusic(BluetoothDevice device, int setid) { + boolean isStreaming = false; + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if ((d.msetID == setid) && !Objects.equals(dm, device)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + synchronized (mBtAcmLock) { + AcmStateMachine sm = mStateMachines.get(dm); + if (sm == null) { + return false; + } + if (sm.isMusicPlaying()) { + isStreaming = true; + Log.d(TAG, "At least one member is streaming for music"); + break; + } + } + } + } + } + } + return isStreaming; + } + + public boolean isShoPendingStop() { + Log.d(TAG, "isShoPendingStop " + mShoPend); + return mShoPend; + } + + public void resetShoPendingStop() { + mShoPend = false; + } + + public boolean isVoiceShoPendingStop() { + Log.d(TAG, "isVoiceShoPendingStop " + mVoiceShoPend); + return mVoiceShoPend; + } + + public void resetVoiceShoPendingStop() { + mVoiceShoPend = false; + } + + public BluetoothDevice getVoiceActiveDevice() { + return mActiveDeviceVoice; + } + + public void removePeersFromBgWl(BluetoothDevice device, int setid) { + synchronized (mStateMachines) { + BluetoothDevice d = null; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + d = i.next(); + if (!(Objects.equals(d, device))) { + Log.d(TAG, "Device: " + d); + AcmStateMachine sm = mStateMachines.get(d); + if (sm == null) { + return; + } + sm.removeDevicefromBgWL(); + } + } + } + } + } + + public boolean isAcmPlayingMusic(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "isAcmPlayingMusic(" + device + ")"); + } + synchronized (mBtAcmLock) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return false; + } + return sm.isMusicPlaying(); + } + } + + public boolean isAcmPlayingVoice(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "isAcmPlayingVoice(" + device + ")"); + } + synchronized (mBtAcmLock) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return false; + } + return sm.isVoicePlaying(); + } + } + + public BluetoothDevice getGroup(BluetoothDevice device) { + Log.d(TAG, "Get group address for (" + device + ")"); + if (device.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "Called for group address"); + return device; + } + BluetoothDevice dm = null; + + if (mAcmDevices != null) { + Log.d(TAG, "Hash Map is not NULL"); + BluetoothAcmDevice d = mAcmDevices.get(device); + if (d != null) + dm = d.mGrpDevice; + } + if (dm == null) { + Log.d(TAG, "Group address is NULL, make New"); + int Id = mCsipManager.getCsipSetId(device, null /*ACM_UUID*/); //TODO: UUID what to set ? + dm = makeGroupBdAddress(device, BluetoothProfile.STATE_DISCONNECTED, Id); + } + return dm; + } + + public int setActiveDevice(BluetoothDevice device, int contextType, int profileType, boolean playReq) { + + Log.d(TAG, "setActiveDevice: " + device + " contextType: " + contextType + " profileType: " + profileType + + " play req: " + playReq + " mActiveDeviceProfile: " + mActiveDeviceProfile+ " mActiveDeviceVoiceProfile: " + mActiveDeviceVoiceProfile); + + if (Objects.equals(device, mActiveDevice) && contextType == CONTEXT_TYPE_MUSIC && (mActiveDeviceProfile == profileType)) { + Log.e(TAG, "setActiveDevice(" + device + "): already set to active for media and profileType same as active profile"); + return ActiveDeviceManagerService.ALREADY_ACTIVE; + } + if (Objects.equals(device, mActiveDeviceVoice) && contextType == CONTEXT_TYPE_VOICE && (mActiveDeviceVoiceProfile == profileType)) { + Log.e(TAG, "setActiveDevice(" + device + "): already set to active for voice and profileType same as active profile"); + return ActiveDeviceManagerService.ALREADY_ACTIVE; + } + if (contextType == CONTEXT_TYPE_MUSIC) { + mShoPend = false; + if ((device == null) && (mActiveDevice != null)) { + if (mActiveDevice.getAddress().contains("9E:8B:00:00:00")) { + byte[] addrByte = Utils.getByteAddress(mActiveDevice); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "isAcmPlayingMusic(addr) " + isAcmPlayingMusic(addr)); + if (isAcmPlayingMusic(addr)) { + mShoPend = true; + break; + } + } + } + } else { + Log.d(TAG, "TWM active device"); + mShoPend = isAcmPlayingMusic(mActiveDevice); + } + } + Log.d(TAG, "mShoPend " + mShoPend); + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceShoPend = false; + if (mActiveDeviceVoice != null) { + if (mActiveDeviceVoice.getAddress().contains("9E:8B:00:00:00")) { + byte[] addrByte = Utils.getByteAddress(mActiveDeviceVoice); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "isAcmPlayingVoice(addr) " + isAcmPlayingVoice(addr)); + if (isAcmPlayingVoice(addr)) { + mVoiceShoPend = true; + break; + } + } + } + } else { + Log.d(TAG, "TWM active device"); + mVoiceShoPend = isAcmPlayingVoice(mActiveDeviceVoice); + } + } + Log.d(TAG, "mVoiceShoPend " + mVoiceShoPend); + } + Log.d(TAG, "old mActiveDevice: " + mActiveDevice + " & old mActiveDeviceVoice: " + mActiveDeviceVoice); + + if (setActiveDeviceAcm(device, contextType, profileType)) { + if (contextType == CONTEXT_TYPE_MUSIC) { + mActiveDevice = device; + mActiveDeviceProfile = profileType; + } else if (contextType == CONTEXT_TYPE_VOICE) { + mActiveDeviceVoice = device; + mActiveDeviceVoiceProfile = profileType; + } + Log.d(TAG, "new mActiveDevice: " + mActiveDevice + " & new mActiveDeviceVoice: " + mActiveDeviceVoice); + if(!playReq) { + if (mShoPend || mVoiceShoPend) { + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_PENDING); + return ActiveDeviceManagerService.SHO_PENDING; + } else { + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_SUCCESS); + return ActiveDeviceManagerService.SHO_SUCCESS; + } + } else { + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_PENDING); + return ActiveDeviceManagerService.SHO_PENDING; + } + } + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_FAILED); + mShoPend = false; + mVoiceShoPend = false; + return ActiveDeviceManagerService.SHO_FAILED; + } + + private boolean setActiveDeviceAcm(BluetoothDevice device, int contextType, int profileType) { + Log.d(TAG, "setActiveDeviceAcm: " + device); + try { + mAcmNativeInterfaceLock.readLock().lock(); + if (mAcmNativeInterface != null && !mAcmNativeInterface.setActiveDevice(device, profileType)) { + Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer"); + return false; + } + } finally { + mAcmNativeInterfaceLock.readLock().unlock(); + } + Log.d(TAG, "setActiveDeviceAcm(" + device + "): returns true"); + return true; + } + + public void setCodecConfigPreference(BluetoothDevice device, + BluetoothCodecConfig codecConfig, int contextType) { + + if (DBG) { + Log.d(TAG, "setCodecConfigPreference(" + device + "): " + + Objects.toString(codecConfig)); + } + mAcmCodecConfig.setCodecConfigPreference(device, codecConfig, contextType); + } + + public void ChangeCodecConfigPreference(BluetoothDevice device, + String mesg) { + + if (DBG) { + Log.d(TAG, "ChangeCodecConfigPreference " + device + "string: " + mesg); + } + if (device == null) + return; + mAcmName = mesg; + synchronized (mStateMachines) { + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (Objects.equals(device, d.mGrpDevice)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + AcmStateMachine sm = getOrCreateStateMachine(dm); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CODEC_CONFIG_CHANGED); + msg.obj = d.msetID; + sm.sendMessage(msg); + } + } + } + } + } + } + + public boolean StartStream(BluetoothDevice device, int contextType) { + if (DBG) { + Log.d(TAG, "startStream for " + device+ " context type: " + contextType); + } + if (device == null) + return false; + synchronized (mStateMachines) { + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (Objects.equals(device, d.mGrpDevice)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + AcmStateMachine sm = getOrCreateStateMachine(dm); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.START_STREAM); + msg.obj = contextType; + sm.sendMessage(msg); + } + } + } + } + } + return true; + } + + public boolean StopStream(BluetoothDevice device, int contextType) { + if (DBG) { + Log.d(TAG, "stopStream for " + device+ " context type: " + contextType); + } + if (device == null) + return false; + synchronized (mStateMachines) { + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (Objects.equals(device, d.mGrpDevice)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + AcmStateMachine sm = getOrCreateStateMachine(dm); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.STOP_STREAM); + msg.obj = contextType; + sm.sendMessage(msg); + } + } + } + } + } + return true; + } + + public void setAbsoluteVolume(BluetoothDevice grpAddr, int volumeLevel, int audioType) { + //Convert volume level to VCP level before sending. + int vcpVolume = convertToVcpVolume(volumeLevel); + BluetoothDevice activeDevice = null; + Log.d(TAG, "AudioVolumeLevel " + volumeLevel + " vcpVolume " + vcpVolume); + + if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO || + audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + ActiveDeviceManagerService activeDeviceManager = + ActiveDeviceManagerService.get(this); + activeDevice = activeDeviceManager.getActiveDevice(audioType); + Log.d(TAG, "activeDevice " + activeDevice + " grpAddr " + grpAddr + + " audioType " + audioType); + if (!grpAddr.equals(activeDevice)) { + Log.e(TAG, "Ignore setAbsoluteVolume for inactive device"); + return; + } + } else if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { + Log.d(TAG, "No active device, grpAddr " + grpAddr + " is broadcast device or group"); + } else { + Log.e(TAG, "Invalid audio type for set volume, ignore this request"); + return; + } + + if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + mActiveDeviceLocalMediaVol = volumeLevel; + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + mActiveDeviceLocalVoiceVol = volumeLevel; + } + + if (grpAddr.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "setAbsoluteVolume for group addr, send abs vol to all members"); + byte[] addrByte = Utils.getByteAddress(grpAddr); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "send vol to member: " + addr); + mVcpController.setAbsoluteVolume(addr, vcpVolume, audioType); + } + } + } else { + Log.d(TAG, "setAbsoluteVolume for single addr, send abs vol to " + grpAddr); + mVcpController.setAbsoluteVolume(grpAddr, vcpVolume, audioType); + } + } + + public void setMute(BluetoothDevice grpAddr, boolean enableMute) { + if (mActiveDevice == null) { + Log.d(TAG, "No active device set, this grpAddr " + grpAddr + " is broadcast group"); + } else { + Log.d(TAG, "mActiveDevice " + mActiveDevice + " grpAddr " + grpAddr); + } + + if (grpAddr.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "setMute for group addr, send mute/unmute to all members"); + byte[] addrByte = Utils.getByteAddress(grpAddr); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "send setMute to member: " + addr); + mVcpController.setMute(addr, enableMute); + } + } + } else { + Log.d(TAG, "setMute for single addr, send mute/unmute to " + grpAddr); + mVcpController.setMute(grpAddr, enableMute); + } + } + + public void onVolumeStateChanged(BluetoothDevice device, int vol, int audioType) { + VolumeManager service = VolumeManager.get(); + //Get or Make group address + BluetoothDevice grpDev = getGroup(device); + + if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { + Log.d(TAG, "volume notification for Broadcast audio"); + } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + Log.d(TAG, "volume notification for Media audio"); + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + Log.d(TAG, "volume notification for Call audio"); + } else { + // remote volume change case + Log.d(TAG, "Volume change from remote: " + device + " vcp vol " + vol); + audioType = service.getActiveAudioType(device); + } + + //Convert vol + int audioVolume = convertToAudioStreamVolume(vol); + Log.d(TAG, "vcp vol " + vol + " audioVolume " + audioVolume); + + if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { + Log.d(TAG, "update volume to APM for Broadcast audio"); + service.onVolumeChange(grpDev, audioVolume, audioType); + } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + //Check if new vol is diff than active group vol + Log.d(TAG, "new vol: " + audioVolume + " mActiveDeviceLocalMediaVol: " + mActiveDeviceLocalMediaVol); + if (mActiveDeviceLocalMediaVol != audioVolume) { + Log.d(TAG, "new vol is different than mActiveDeviceLocalMediaVol update APM"); + service.onVolumeChange(grpDev, audioVolume, audioType); + mActiveDeviceLocalMediaVol = audioVolume; + } else { + Log.d(TAG, "local active media vol same as device new vol, ignore sending to APM"); + } + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + Log.d(TAG, "new vol: " + audioVolume + " mActiveDeviceLocalVoiceVol: " + mActiveDeviceLocalVoiceVol); + if (mActiveDeviceLocalVoiceVol != audioVolume) { + Log.d(TAG, "new vol is different than mActiveDeviceLocalVoiceVol update APM"); + service.onVolumeChange(grpDev, audioVolume, audioType); + mActiveDeviceLocalVoiceVol = audioVolume; + } else { + Log.d(TAG, "local active call vol same as device new vol, ignore sending to APM"); + } + } else { + Log.d(TAG, "No audio is streaming and is inactive device, ignore sending to APM"); + } + + //Check if this device is group device, then take lock (ignored for now) and update vol to other members as well. + applyVolToOtherMembers(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/), vol, audioType); + } + + public void onMuteStatusChanged(BluetoothDevice device, boolean isMuted) { + VolumeManager service = VolumeManager.get(); + //Get or Make group address + BluetoothDevice grpDev = getGroup(device); + //default context type + int c_type = CONTEXT_TYPE_UNKNOWN; + + Log.d(TAG, "isMuted " + isMuted + " mActiveDeviceIsMuted " + mActiveDeviceIsMuted); + + if (!mVcpController.isBroadcastDevice(device) && ((mActiveDevice == null) || (mActiveDevice != grpDev))) { + Log.d(TAG, "unicast-mode, either Active device null or muteStatus changed for inactive device -- return"); + return; + } + + if ((mActiveDevice == null) || (mActiveDevice != grpDev)) { + Log.d(TAG, "No Active device or device is not active, seems in Broadcast mode"); + c_type = CONTEXT_TYPE_BROADCAST_AUDIO; + } else { + Log.d(TAG, "mActiveDevice " + mActiveDevice + " grpDev " + grpDev + " device " + device); + c_type = CONTEXT_TYPE_MUSIC; + } + + //Check if new mute state is diff than active group mute state + if (mActiveDeviceIsMuted != isMuted) { + Log.d(TAG, "new mute state is different than mActiveDeviceIsMuted update APM"); + service.onMuteStatusChange(grpDev, isMuted, CONTEXT_TYPE_MUSIC); + mActiveDeviceIsMuted = isMuted; + } else { + Log.d(TAG, "local active device mute state same as device new mute state, ignore sending to APM, but send to other members"); + } + + //Check if this device is group device, then take lock(ignored for now) and update mute state to other members as well. + applyMuteStateToOtherMembers(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/), isMuted); + } + + public void setAbsVolSupport(BluetoothDevice device, boolean isAbsVolSupported, int initialVol) { + VolumeManager service = VolumeManager.get(); + //Get or Make group address + BluetoothDevice grpDev = getGroup(device); + if (grpDev == null) { + Log.d(TAG, "Group not created, return"); + return; + } + if ((mVcpController.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) && isAbsVolSupported) { + if (!isVcpPeerDeviceConnected(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/))) { + Log.d(TAG, "VCP Peer device not connected, this is 1st member, update support to APM "); + //get current vol from VCP and send to APM during connection. + Log.d(TAG, "VCP initialVol " + initialVol + " when connected device " + device); + Log.d(TAG, "Update APM with connection vol " + convertToAudioStreamVolume(initialVol)); + service.setAbsoluteVolumeSupport(grpDev, isAbsVolSupported, convertToAudioStreamVolume(initialVol), ApmConst.AudioProfiles.VCP); + + //get current mute state from VCP and sent to APM during connection. + Log.d(TAG, "VCP mute state " + mVcpController.isMute(device) + " when connected device " + device); + service.onMuteStatusChange(grpDev, mVcpController.isMute(device), CONTEXT_TYPE_MUSIC); + } else { + Log.d(TAG, "VCP Peer device connected, this is not 1st member, skip update to APM"); + } + } else if ((mVcpController.getConnectionState(device) == BluetoothProfile.STATE_DISCONNECTED) && !isAbsVolSupported) { + if (isVcpPeerDeviceConnected(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/))) { + Log.d(TAG, "VCP Peer device connected, this is not last member, skip update to APM "); + } else { + Log.d(TAG, "VCP Peer device not connected, this is last member, update to APM"); + service.setAbsoluteVolumeSupport(grpDev, isAbsVolSupported, ApmConst.AudioProfiles.VCP); + } + } + } + + public boolean isVcpPeerDeviceConnected(BluetoothDevice device, int setid) { + boolean isVcpPeerConnected = false; + if (setid == INVALID_SET_ID) + return isVcpPeerConnected; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return isVcpPeerConnected; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + if (!Objects.equals(addr, device) && (mVcpController.getConnectionState(addr) == BluetoothProfile.STATE_CONNECTED)) { + isVcpPeerConnected = true; + Log.d(TAG, "At least one other vcp member is in connected state"); + break; + } + } + } + return isVcpPeerConnected; + } + + private int convertToAudioStreamVolume(int volume) { + // Rescale volume to match AudioSystem's volume + return (int) Math.round((double) volume * mAudioStreamMax / VCP_MAX_VOL); + } + + private int convertToVcpVolume(int volume) { + return (int) Math.ceil((double) volume * VCP_MAX_VOL / mAudioStreamMax); + } + + private void applyVolToOtherMembers(BluetoothDevice device, int setid, int volume, int audioType) { + if (setid == INVALID_SET_ID) + return; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + if (!Objects.equals(addr, device)) { + Log.d(TAG, "send vol changed to addr " + addr); + mVcpController.setAbsoluteVolume(addr, volume, audioType); + } + } + } + } + + private void applyMuteStateToOtherMembers(BluetoothDevice device, int setid, boolean muteState) { + if (setid == INVALID_SET_ID) + return; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + if (!Objects.equals(addr, device)) { + Log.d(TAG, "send mute state to addr " + addr); + mVcpController.setMute(addr, muteState); + } + } + } + } + + public int getVcpConnState(BluetoothDevice device) { + return mVcpController.getConnectionState(device); + } + + public int getVcpConnMode(BluetoothDevice device) { + return mVcpController.getConnectionMode(device); + } + + public boolean isVcpMute(BluetoothDevice device) { + return mVcpController.isMute(device); + } + + public String getAcmName() { + return mAcmName; + } + + private static class AcmBinder extends Binder implements IProfileServiceBinder { + AcmBinder(AcmService service) { + } + @Override + public void cleanup() { + } + } + + private BluetoothGroupCallback mBluetoothGroupCallback = new BluetoothGroupCallback() { + public void onGroupClientAppRegistered(int status, int appId) { + Log.d(TAG, "onGroupClientAppRegistered, status: " + status + "appId: " + appId); + if (status == 0) { + mCsipManager.updateAppId(appId); + mIsCsipRegistered = true; + } else { + Log.e(TAG, "DeviceGroup registeration failed, status:" + status); + } + } + + public void onConnectionStateChanged (int state, BluetoothDevice device) { + Log.d(TAG, "onConnectionStateChanged: Device: " + device + "state: " + state); + //notify statemachine about device connection state + synchronized (mStateMachines) { + AcmStateMachine sm = getOrCreateStateMachine(device); + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_CONNECTION_STATE_CHANGED); + msg.obj = state; + sm.sendMessage(msg); + } + } + + public void onExclusiveAccessChanged (int setId, int value, int status, + List devices) { + Log.d(TAG, "onExclusiveAccessChanged: setId" + setId + devices + "status:" + status); + + //notify the statemachine about CSIP Lock status + switch (status) { + case ALL_LOCKS_ACQUIRED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_LOCKED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + break; + } + + case SOME_LOCKS_ACQUIRED_REASON_TIMEOUT: //check if need to handle separately + case SOME_LOCKS_ACQUIRED_REASON_DISC: { + BluetoothDevice mDevice = getCsipLockRequestedDevice(); + if (devices.contains(mDevice)) { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_PARTIAL_LOCK); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } else { + Log.d(TAG, "Exclusive access requested device is not present in list, release access for all devices"); + mCsipManager.setLock(setId, devices, mCsipManager.UNLOCK); + } + } break; + + case LOCK_DENIED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_DENIED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break; + + case LOCK_RELEASED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break; + + case LOCK_RELEASED_TIMEOUT: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break; + + /*case INVALID_REQUEST_PARAMS: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_INVALID_PARAM); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break;*/ + + /*case LOCK_RELEASE_NOT_ALLOWED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASE); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break;*/ + + case INVALID_VALUE: + break; + } + } + + //public void onSetMemberFound (int setId, BluetoothDevice device) { } + + //public void onSetDiscoveryStatusChanged (int setId, int status, int reason) { } + + //public void onLockAvailable (int setId, BluetoothDevice device) { } + + //public void onNewSetFound (int setId, BluetoothDevice device, UUID uuid) { } + + //public void onLockStatusFetched (int setId, int lockStatus) { } + }; + + public CsipManager getCsipManager(){ + return mCsipManager; + } + + class CsipManager { + public int LOCK = 0; + public int UNLOCK = 0; + + int mCsipAppid; + + CsipManager() { + LOCK = BluetoothDeviceGroup.ACCESS_GRANTED; + UNLOCK = BluetoothDeviceGroup.ACCESS_RELEASED; + + registerCsip(); + } + + CsipManager(BluetoothGroupCallback callbacks) { + LOCK = BluetoothDeviceGroup.ACCESS_GRANTED; + UNLOCK = BluetoothDeviceGroup.ACCESS_RELEASED; + + registerCsip(callbacks); + } + + void updateAppId(int id) { + mCsipAppid = id; + } + + int getAppId() { + return mCsipAppid; + } + + void registerCsip() { + GroupService mGroupService = GroupService.getGroupService(); + if(mGroupService != null) { + Log.i(TAG, "mGroupService is not null, register"); + mGroupService.registerGroupClientModule(mBluetoothGroupCallback); + } + } + + void registerCsip(BluetoothGroupCallback callbacks) { + GroupService mGroupService = GroupService.getGroupService(); + if(mGroupService != null) { + Log.i(TAG, "mGroupService is not null, register " + callbacks); + mGroupService.registerGroupClientModule(callbacks); + } + } + + void unregisterCsip() { + GroupService mGroupService = GroupService.getGroupService(); + if (mGroupService != null) + mGroupService.unregisterGroupClientModule(mCsipAppid); + } + + void connectCsip(BluetoothDevice device) { + GroupService mGroupService = GroupService.getGroupService(); + mGroupService.connect(mCsipAppid, device); + } + + void disconnectCsip(BluetoothDevice device) { + GroupService mGroupService = GroupService.getGroupService(); + mGroupService.disconnect(mCsipAppid, device); + } + + void setLock(int setId, List devices, int lockVal) { + GroupService mGroupService = GroupService.getGroupService(); + mGroupService.setLockValue(mCsipAppid, setId, devices, lockVal); + } + + DeviceGroup getCsipSet(int setId) { + GroupService mGroupService = GroupService.getGroupService(); + return mGroupService.getCoordinatedSet(setId); + } + + List getSetMembers(int setId) { + DeviceGroup set = getCsipSet(setId); + if(set != null) + return set.getDeviceGroupMembers(); + return null; + } + + int getCsipSetId(BluetoothDevice device, ParcelUuid uuid) { + GroupService mGroupService = GroupService.getGroupService(); + return mGroupService.getRemoteDeviceGroupId(device, uuid); + //return -1; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..d2c39fbc552798310be9527238c9233a3a7d39f4 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java @@ -0,0 +1,144 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.acm; + +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; + +/** + * Stack event sent via a callback from JNI to Java, or generated + * internally by the ACM State Machine. + */ +public class AcmStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + public static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2; + public static final int EVENT_TYPE_CODEC_CONFIG_CHANGED = 3; + + // Do not modify without updating the HAL bt_acm.h files. + // Match up with btacm_connection_state_t enum of bt_acm.h + static final int CONNECTION_STATE_DISCONNECTED = 0; + static final int CONNECTION_STATE_CONNECTING = 1; + static final int CONNECTION_STATE_CONNECTED = 2; + static final int CONNECTION_STATE_DISCONNECTING = 3; + // Match up with btacm_audio_state_t enum of bt_acm.h + static final int AUDIO_STATE_REMOTE_SUSPEND = 0; + static final int AUDIO_STATE_STOPPED = 1; + static final int AUDIO_STATE_STARTED = 2; + + // Match up with btacm_audio_state_t enum of bt_acm.h + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + + // Match up with btacm_audio_state_t enum of bt_acm.h + static final int PROFILE_TYPE_NONE = 0; + + public int type = EVENT_TYPE_NONE; + public BluetoothDevice device; + public int valueInt1 = 0; + public int valueInt2 = 0; + public BluetoothCodecStatus codecStatus; + + AcmStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("AcmStackEvent {type:" + eventTypeToString(type)); + result.append(", device:" + device); + result.append(", state:" + eventTypeValueIntToString(type, valueInt1)); + result.append(", context type:" + contextTypeValueIntToString(valueInt2)); + if (codecStatus != null) { + result.append(", codecStatus:" + codecStatus); + } + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + return "EVENT_TYPE_CONNECTION_STATE_CHANGED"; + case EVENT_TYPE_AUDIO_STATE_CHANGED: + return "EVENT_TYPE_AUDIO_STATE_CHANGED"; + case EVENT_TYPE_CODEC_CONFIG_CHANGED: + return "EVENT_TYPE_CODEC_CONFIG_CHANGED"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } + + private static String eventTypeValueIntToString(int type, int value) { + switch (type) { + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + switch (value) { + case CONNECTION_STATE_DISCONNECTED: + return "DISCONNECTED"; + case CONNECTION_STATE_CONNECTING: + return "CONNECTING"; + case CONNECTION_STATE_CONNECTED: + return "CONNECTED"; + case CONNECTION_STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + break; + case EVENT_TYPE_AUDIO_STATE_CHANGED: + switch (value) { + case AUDIO_STATE_REMOTE_SUSPEND: + return "REMOTE_SUSPEND"; + case AUDIO_STATE_STOPPED: + return "STOPPED"; + case AUDIO_STATE_STARTED: + return "STARTED"; + default: + break; + } + break; + default: + break; + } + return Integer.toString(value); + } + + private static String contextTypeValueIntToString(int value) { + switch (value) { + case CONTEXT_TYPE_UNKNOWN: + return "UNKNOWN"; + case CONTEXT_TYPE_MUSIC: + return "MEDIA"; + case CONTEXT_TYPE_VOICE: + return "CONVERSATIONAL"; + case CONTEXT_TYPE_MUSIC_VOICE: + return "MEDIA+CONVERSATIONAL"; + default: + return "UNKNOWN:" + value; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java new file mode 100644 index 0000000000000000000000000000000000000000..293387cb5e29044f0b603e9f574e6a84ba74fb97 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java @@ -0,0 +1,2354 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.acm; +//package com.android.bluetooth.apm; + +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import com.android.internal.util.IState; +import com.android.bluetooth.BluetoothStatsLog; +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import java.util.UUID; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import android.os.SystemProperties; +import com.android.bluetooth.btservice.AdapterService; +import java.util.Objects; +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.apm.StreamAudioService; +import com.android.bluetooth.vcp.VcpController; +import android.bluetooth.BluetoothVcp; + +final class AcmStateMachine extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "AcmStateMachine"; + private String mReconfig = ""; + public UUID uuid; + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int CSIP_CONNECTION_STATE_CHANGED = 3; + static final int CSIP_LOCK_STATUS_LOCKED = 4; + static final int CSIP_LOCK_STATUS_PARTIAL_LOCK = 5; + static final int CSIP_LOCK_STATUS_DENIED = 6; + static final int CSIP_LOCK_STATUS_RELEASED = 7; + static final int CODEC_CONFIG_CHANGED = 8; + static final int GATT_CONNECTION_STATE_CHANGED = 9; + static final int GATT_CONNECTION_TIMEOUT = 10; + static final int START_STREAM = 11; + static final int STOP_STREAM = 12; + static final int START_STREAM_REQ = 13; + @VisibleForTesting + static final int STACK_EVENT = 101; + private static final int CONNECT_TIMEOUT = 201; + private static final int CSIP_LOCK_RELEASE_TIMEOUT = 301; + private static final int CSIP_LOCK_TIMEOUT = 401; + private static final int LE_AUDIO_AVAILABLE_LICENSED = 0x00000300; + static final int GATT_CONNECTION_TIMEOUT_MS = 30000; + static final int GATT_PEER_CONN_TIMEOUT = 8; + static final int GATT_CONN_FAILED_ESTABLISHED = 0x3E; + + @VisibleForTesting + static int sConnectTimeoutMs = 30000; // 30s + static int sCsipLockReleaseTimeoutMs = 5000; //TODO + static int sCsipLockTimeoutMs = 5000; + + private final int ACM_MAX_BYTES = 100; + + private final int CS_PARAM_NUM_BITS = 8; + private final int CS_PARAM_IND_MASK = 0xff; + private final int CS_PARAM_1ST_INDEX = 0x00; + private final int CS_PARAM_2ND_INDEX = 0x01; + private final int CS_PARAM_3RD_INDEX = 0x02; + private final int CS_PARAM_4TH_INDEX = 0x03; + private final int CS_PARAM_5TH_INDEX = 0x04; + private final int CS_PARAM_6TH_INDEX = 0x05; + private final int CS_PARAM_7TH_INDEX = 0x06; + private final int CS_PARAM_8TH_INDEX = 0x07; + + private final int CODEC_TYPE_LC3Q = 0x10; + + private static final String CODEC_NAME = "Codec"; + private static final String STREAM_MAP = "StreamMap"; + private static final String FRAME_DURATION = "FrameDuration"; + private static final String SDU_BLOCK = "Blocks_forSDU"; + private static final String RXCONFIG_INDX = "rxconfig_index"; + private static final String TXCONFIG_INDX = "txconfig_index"; + private static final String VERSION = "version"; + private static final String VENDOR_META_DATA = "vendor"; + private Disconnected mDisconnected; + private Connecting mConnecting; + private Disconnecting mDisconnecting; + private Connected mConnected; + private Streaming mStreaming; + private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private int mLastConnectionState = -1; + private int mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private int mLastMusicConnectionState = -1; + private int mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private int mLastVoiceConnectionState = -1; + + private AcmService mAcmService; + + private AcmNativeInterface mAcmNativeInterface; + private BluetoothGatt mBluetoothGatt; + private final BluetoothDevice mDevice; + private BluetoothDevice mGroupAddress; + private boolean mIsMusicPlaying = false; + private boolean mIsVoicePlaying = false; + private VcpController mVcpController; + private BluetoothCodecStatus mMusicCodecStatus; + private BluetoothCodecStatus mVoiceCodecStatus; + + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + + private int mContextTypeToDisconnect = CONTEXT_TYPE_UNKNOWN; + private int mCurrentContextType = CONTEXT_TYPE_UNKNOWN; + private int mProfileType = 0; + private int mPreferredContext = CONTEXT_TYPE_UNKNOWN; + + private boolean IsDisconnectRequested = false; + private boolean IsReconfigRequested = false; + private int mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private boolean mDeviceLocked = false; + private boolean mCsipLockRequested = false; + private boolean mIsDeviceWhitelisted = false; + private int mSetId; + private boolean mAcmMTUChangeRequested = false; + private int cached_state; + private boolean mIsUpdateProfDisConnection = false; + + AcmStateMachine(BluetoothDevice device, AcmService acmService, + AcmNativeInterface acmNativeInterface, Looper looper) { + super(TAG, looper); + setDbg(DBG); + mDevice = device; + mAcmService = acmService; + mAcmNativeInterface = acmNativeInterface; + + mDisconnected = new Disconnected(); + mConnecting = new Connecting(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + mStreaming = new Streaming(); + + addState(mDisconnected); + addState(mConnecting); + addState(mDisconnecting); + addState(mConnected); + addState(mStreaming); + + mCurrentContextType = CONTEXT_TYPE_UNKNOWN; + mDeviceLocked = false; + IsDisconnectRequested = false; + IsReconfigRequested = false; + mIsDeviceWhitelisted = false; + setInitialState(mDisconnected); + mSetId = -1; + cached_state = -1; + mAcmMTUChangeRequested = false; + mIsUpdateProfDisConnection = false; + + if (mBluetoothGatt != null) { + Log.e(TAG, "disconnect gatt"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + + static AcmStateMachine make(BluetoothDevice device, AcmService acmService, + AcmNativeInterface acmNativeInterface, Looper looper) { + Log.i(TAG, "make acm for device " + device); + AcmStateMachine acmSm = new AcmStateMachine(device, acmService, acmNativeInterface, + looper); + acmSm.start(); + return acmSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + if (mIsMusicPlaying) { + // Stop if audio is still playing + log("doQuit: stopped Music playing " + mDevice); + mIsMusicPlaying = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (service != null) + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + } + if (mIsVoicePlaying) { + log("doQuit: stopped voice streaming " + mDevice); + mIsVoicePlaying = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (service != null) + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.CALL_AUDIO); + } + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + if (mBluetoothGatt != null) { + Log.e(TAG, "disconnect gatt"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + Log.i(TAG, "onConnectionStateChange: Status: " + status + " newState: " + newState); + if (status == BluetoothGatt.GATT_SUCCESS /* || status == 0x16 */) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + mBluetoothGatt.requestMtu(ACM_MAX_BYTES); + mAcmMTUChangeRequested = true; + cached_state = newState; + } else { + if (mAcmMTUChangeRequested == true) { + mAcmMTUChangeRequested = false; + } + + if (cached_state != -1) { + cached_state = -1; + } + + Message m = obtainMessage(GATT_CONNECTION_STATE_CHANGED); + m.obj = newState; + sendMessage(m); + } + } else if (status == GATT_PEER_CONN_TIMEOUT || + status == GATT_CONN_FAILED_ESTABLISHED) { + BluetoothDevice target = gatt.getDevice(); + Log.i(TAG, "[ACM] remote side disconnection, for device add in BG WL: " +target); + mBluetoothGatt.close(); + mBluetoothGatt = target.connectGatt(mAcmService, true, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + mIsDeviceWhitelisted = true; + } else { + mBluetoothGatt.close(); + mBluetoothGatt = null; + Log.i(TAG, "[ACM] Failed to connect GATT server."); + } + } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + log("onMtuChanged: mtu: " + mtu + + " mAcmMTUChangeRequested: " + mAcmMTUChangeRequested + + " cached_state: " + cached_state); + if (mAcmMTUChangeRequested == true) { + if (cached_state != -1) { + Message m = obtainMessage(GATT_CONNECTION_STATE_CHANGED); + m.obj = cached_state; + sendMessage(m); + mAcmMTUChangeRequested = false; + cached_state = -1; + } + } else { + log("onMtuChanged: Remote initiated trigger"); + //Do nothing + } + } + }; + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + //Disconnect unicast VCP 1st + mVcpController = VcpController.getVcpController(); + if (mVcpController != null) { + Log.d(TAG, "Disconnect VCP for " + mDevice); + mVcpController.disconnect(mDevice, BluetoothVcp.MODE_UNICAST); + } else { + Log.d(TAG, "mVcpController is null"); + } + + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + removeDeferredMessages(DISCONNECT); + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + removeMessages(GATT_CONNECTION_TIMEOUT); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mLastMusicConnectionState != -1) { + // Don't broadcast during startup + if (mIsMusicPlaying) { + Log.i(TAG, "Disconnected: stop music streaming: " + mDevice); + mIsMusicPlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + } + if (mLastVoiceConnectionState != -1) { + if (mIsVoicePlaying) { + Log.i(TAG, "Disconnected: stop voice streaming: " + mDevice); + mIsVoicePlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + // ensure when one device is disconnected, need to update the channel mode. + mAcmService.updateLeaChannelMode(BluetoothA2dp.STATE_NOT_PLAYING, mDevice); + //Unlock, device state changed from connected to disconnected + + Log.i(TAG, " mIsUpdateProfDisConnection: " + mIsUpdateProfDisConnection); + if (mDevice.getBondState() == BluetoothDevice.BOND_NONE || + mIsUpdateProfDisConnection) { + Log.i(TAG, "Device is unpaired/mIsUpdateProfDisConnection has been set," + + " update disconnected to APM for " + mDevice); + mIsUpdateProfDisConnection = false; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + mDeviceLocked = false; + //assuming disconnected state we are moving hence update AcmDevice hash map + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + } else if (mDeviceLocked) { + Log.i(TAG, "Access RELEASED for device " + mDevice); + List members = new ArrayList(); + members.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + } else if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) { + Log.i(TAG, "Device access is already RELEASED, Go for DeviceGroup Disconnect " + mDevice); + mAcmService.getCsipManager().disconnectCsip(mDevice); + } else { + if (mBluetoothGatt != null && !mIsDeviceWhitelisted) { + Log.d(TAG, "remove other devices from BG WL"); + mAcmService.removePeersFromBgWl(mDevice, mSetId); + Log.i(TAG, "Go for GATT Disconnect " + mDevice); + mBluetoothGatt.disconnect(); + } else { + Log.d(TAG, "mBluetoothGatt is NULL or device is in BG WL "); + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect"); + if (!mIsDeviceWhitelisted) { + mAcmService.removePeersFromBgWl(mDevice, mSetId); + Log.d(TAG, "remove other devices from BG WL"); + } + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + //assuming disconnected state we are moving hence update AcmDevice hash map + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + } + } + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + mConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mLastMusicConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mLastVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + mCurrentContextType = message.arg1; + mProfileType = message.arg2; + mPreferredContext = (int)message.obj; + Log.i(TAG, "Connecting " + contextTypeToString(mCurrentContextType) + " to " + mDevice); + if (mAcmService.IsLockSupportAvailable(mDevice)) { + Log.d(TAG, "Exclusive Access support available, go for GATT connect"); + //if lock support available then go for CSIP connect + if (mBluetoothGatt != null) { + mBluetoothGatt.close(); + } + mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + + sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + transitionTo(mConnecting); + break; + } else { + Log.d(TAG, "Exclusive Access support not available, go for GATT connect"); + if (mBluetoothGatt != null) { + mBluetoothGatt.close(); + } + mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + + sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + transitionTo(mConnecting); + /*if (!mAcmNativeInterface.connectAcm(mDevice, message.arg1, message.arg2, (int)message.obj)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + transitionTo(mConnecting);*/ + } + } break; + + case GATT_CONNECTION_STATE_CHANGED: { + removeMessages(GATT_CONNECTION_TIMEOUT); + int st = (int)message.obj; + if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "GATT Disconnected"); + mIsDeviceWhitelisted = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mCurrentContextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect MEDIA"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_MUSIC_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect MEDIA+VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + //assuming disconneted state we are moving hence update AcmDevice hash map + mAcmService.handleAcmDeviceStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTED, mSetId); + mBluetoothGatt.close(); + } else if (st == BluetoothProfile.STATE_CONNECTED) { + mIsDeviceWhitelisted = false; + Log.d(TAG, "GATT connected from background, go for profile connection"); + AdapterService mAdapterService = AdapterService.getAdapterService(); + if (mAdapterService != null && !mAdapterService.connectAllEnabledProfiles(mDevice)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + break; + } + } break; + + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + + case GATT_CONNECTION_TIMEOUT: { + Log.d(TAG, "GATT connection Timeout"); + break; + } + + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_DISCONNECTED) + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + if (mBluetoothGatt != null) + mBluetoothGatt.disconnect(); + break; + + case CSIP_LOCK_STATUS_RELEASED: { + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + //disconnect CSIP + int value = (int)message.arg1; + Log.d(TAG, "Exclusive Access state changed:" + value); + if (value == mAcmService.getCsipManager().UNLOCK) { + if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) { + mAcmService.getCsipManager().disconnectCsip(mDevice); + } + } + mCsipLockRequested = false; + mDeviceLocked = false; + break; + } + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Disconnected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnected state + private void processConnectionEvent(int state, int contextType) { + switch (state) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Ignore ACM DISCONNECTED event: " + mDevice); + break; + case AcmStackEvent.CONNECTION_STATE_CONNECTING:{ + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, contextType); + } + break; + case AcmStackEvent.CONNECTION_STATE_CONNECTED: {//Shouldn't come + Log.w(TAG, "ACM Connected from Disconnected state: " + mDevice); + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming ACM Connected request rejected: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, contextType); + } + break; + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Ignore ACM DISCONNECTING event: " + mDevice); + break; + default: + Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice); + break; + } + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + //Connect unicast VCP 1st + mVcpController = VcpController.getVcpController(); + if (mVcpController != null) { + Log.d(TAG, "Connect VCP for " + mDevice); + mVcpController.connect(mDevice, BluetoothVcp.MODE_UNICAST); + } else { + Log.d(TAG, "mVcpController is null"); + } + + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + //this context type here is global context type, if not based on current + //and prev states per context, form this contextType here + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_CONNECTING; + } + mSetId = mAcmService.getCsipManager().getCsipSetId(mDevice, null /*ACM_UUID*/); //TODO: UUID what to set ? + mGroupAddress = mAcmService.getGroup(mDevice); + Log.d(TAG, "Group bd address " + mGroupAddress); + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + if (mCurrentContextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not first member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not first member "); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_MUSIC_VOICE) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not first member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC & VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + if (mCurrentContextType == CONTEXT_TYPE_MUSIC) { + mLastMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + } else if (mCurrentContextType == CONTEXT_TYPE_VOICE) { + mLastVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + } else if (mCurrentContextType == CONTEXT_TYPE_MUSIC_VOICE) { + mLastMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + mLastVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + } + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); // Do we need to defer this ? It shouldn't have come. + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Connecting connection timeout: " + mDevice); + //check if CSIP is connected + if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) + mAcmService.getCsipManager().disconnectCsip(mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, mCurrentContextType); + AcmStackEvent event = + new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = mDevice; + event.valueInt1 = AcmStackEvent.CONNECTION_STATE_DISCONNECTED; + event.valueInt2 = mCurrentContextType; + sendMessage(STACK_EVENT, event); + break; + } + case DISCONNECT: { + // Cancel connection, shouldn't come + mContextTypeToDisconnect = (int)message.obj; + //check if disconnect is for individual context type + IState state = mDisconnected; + Log.i(TAG, "Connecting: connection canceled to " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTING) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTING)) { + if (mContextTypeToDisconnect != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connecting state but broadcast the connection state*/ + state = mConnecting; + } else { + //disconnect is for both context type then disconnect CSIP + if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) + mAcmService.getCsipManager().disconnectCsip(mDevice); + } + } + processTransitionContextState(mConnecting, mDisconnected, mContextTypeToDisconnect); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (state == mConnecting) { + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC) + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + transitionTo(state); + } + break; + } + + case GATT_CONNECTION_STATE_CHANGED: { + removeMessages(GATT_CONNECTION_TIMEOUT); + int st = (int)message.obj; + if (st == BluetoothProfile.STATE_CONNECTED) { + mIsDeviceWhitelisted = false; + Log.d(TAG, "GATT connected, go for connect"); + if (!mAcmNativeInterface.connectAcm(mDevice, mCurrentContextType, mProfileType, mPreferredContext)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + } else if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "GATT Disconnected"); + mIsDeviceWhitelisted = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + transitionTo(mDisconnected); + } + /*if (st == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "GATT connected, go for DeviceGroup connect"); + mAcmService.getCsipManager().connectCsip(mDevice); + } else if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "GATT Disconnected"); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + }*/ + } break; + + case GATT_CONNECTION_TIMEOUT: { + Log.d(TAG, "GATT connection Timeout"); + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Peer device is connected, add to BG WL"); + mIsDeviceWhitelisted = true; + mBluetoothGatt.close(); + + mBluetoothGatt = mDevice.connectGatt(mAcmService, true, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + + } else { + Log.d(TAG, "No member is in connected state, do not add in BG WL"); + mIsDeviceWhitelisted = false; + if (mBluetoothGatt != null) { + Log.e(TAG, "Disconnect gatt and make gatt instance null."); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + transitionTo(mDisconnected); + break; + } + + case CSIP_CONNECTION_STATE_CHANGED: { + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.e(TAG, "DeviceGroup Connected to " + mDevice); + mCsipConnectionState = BluetoothProfile.STATE_CONNECTED; + mSetId = mAcmService.getCsipManager().getCsipSetId(mDevice, null /*ACM_UUID*/); //TODO: UUID what to set ? + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + List members = new ArrayList(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + //setlockvalue takes device list + mCsipLockRequested = true; + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK); + } else { + Log.e(TAG, "DeviceGroup Connection failed to " + mDevice); + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mCsipLockRequested = false; + mDeviceLocked = false; + transitionTo(mDisconnected); + } + break; + } + + case CSIP_LOCK_STATUS_LOCKED: { + mCsipLockRequested = false; + int value = (int)message.arg1; + Log.d(TAG, "Exclusive Access state changed:" + value); + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + break; + } + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (!mAcmNativeInterface.connectAcm(mDevice, mCurrentContextType, mProfileType, mPreferredContext)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + mAcmService.getCsipManager().disconnectCsip(mDevice); + transitionTo(mDisconnected); + break; + } + } else { + mDeviceLocked = false; + Log.w(TAG, "Exclusive Access failed to " + setId); + transitionTo(mDisconnected); + } + break; + } + + case CSIP_LOCK_STATUS_PARTIAL_LOCK: { + //check if requested lock is from this device + if (mCsipLockRequested) { + int value = (int)message.arg1; + Log.d(TAG, "Exclusive Access state changed:" + value); + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if (mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + break; + } + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (!mAcmNativeInterface.connectAcm(mDevice, mCurrentContextType, mProfileType, mPreferredContext)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + mAcmService.getCsipManager().disconnectCsip(mDevice); + transitionTo(mDisconnected); + break; + } + } else { + mDeviceLocked = false; + Log.w(TAG, "Exclusive Access failed to " + setId); + transitionTo(mDisconnected); + } + mCsipLockRequested = false; + } else { + //lock is not requested from this device TODO: check if + Log.d(TAG, "Exclusive Access is not requested from this device: " + mDevice); + mDeviceLocked = true; + } + } + break; + + case CSIP_LOCK_RELEASE_TIMEOUT: + //TODO: lock release individual ? + Log.d(TAG, "Exclusive Access timeout to " + mDevice); + List set = new ArrayList(); + set.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, set, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_DENIED: + //lock denied fail connection + Log.e(TAG, "DeviceGroup Connection failed to " + mDevice); + mCsipLockRequested = false; + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_RELEASED: + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + mCsipLockRequested = false; + mDeviceLocked = false; + break; + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Connecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + break; + default: + Log.e(TAG, "Connecting: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connecting state + private void processConnectionEvent(int state, int contextType) { + IState smState; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + switch (state) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + smState = mDisconnected; + Log.w(TAG, "Connecting device disconnected: " + mDevice); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTING) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTING)) { + if (contextType != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, remain in connecting state but broadcast the connection state*/ + smState = mConnecting; + } + } + processTransitionContextState(mConnecting, mDisconnected, contextType); + if (smState == mConnecting) { + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } else { + transitionTo(smState); + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + // start lock release timer TODO:when CSIP support is available + //sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + if (contextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + transitionTo(mConnected); + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTING: + // Ignored - probably an event that the outgoing connection was initiated + break; + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: { + Log.w(TAG, "Connecting device disconnecting: " + mDevice); + transitionTo(mDisconnecting); + } break; + + default: + Log.e(TAG, "Incorrect event: " + state); + break; + } + } + } + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + //Disconnect unicast VCP 1st + mVcpController = VcpController.getVcpController(); + if (mVcpController != null) { + Log.d(TAG, "Disconnect VCP for " + mDevice); + mVcpController.disconnect(mDevice, BluetoothVcp.MODE_UNICAST); + } else { + Log.d(TAG, "mVcpController is null"); + } + + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_DISCONNECTING; + } + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) { + mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MEDIA"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MEDIA"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) { + mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE "); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC_VOICE && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED + && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) { + mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTING; + mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MEDIA+VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update MEDIA+VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Disconnecting connection timeout: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, mCurrentContextType); + AcmStackEvent event = + new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = mDevice; + event.valueInt1 = AcmStackEvent.CONNECTION_STATE_DISCONNECTED; + event.valueInt2 = mCurrentContextType; + sendMessage(STACK_EVENT, event); + break; + } + case DISCONNECT: + deferMessage(message); + break; + case GATT_CONNECTION_STATE_CHANGED: + deferMessage(message); + break; + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Disconnecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + default: + Log.e(TAG, "Disconnecting: ignoring stack event: " + event); + break; + } + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnecting state + private void processConnectionEvent(int event, int contextType) { + IState smState; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + switch (event) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + Log.w(TAG, "Disconnecting device disconnected " + mDevice); + processTransitionContextState(mDisconnecting, mDisconnected, contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + if (mMusicConnectionState == BluetoothProfile.STATE_DISCONNECTED && + mVoiceConnectionState == BluetoothProfile.STATE_DISCONNECTED) { + transitionTo(mDisconnected); + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + //TODO:sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming ACM Connected request rejected: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, contextType); + } break; + + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: + if (contextType == CONTEXT_TYPE_MUSIC) + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC, false); + else if (contextType == CONTEXT_TYPE_VOICE) + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_VOICE, false); + else { + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_VOICE, false); + } + Log.d(TAG, "Updating disconnecting state to APM " + mDevice + "contextType " + contextType); + IsDisconnectRequested = false; + // We are already disconnecting, do nothing + break; + default: + Log.e(TAG, "Incorrect event: " + event); + break; + } + } + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_CONNECTED; + } + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + if (message.arg1 == CONTEXT_TYPE_MUSIC && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_MUSIC_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED + && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + mCurrentContextType += message.arg1; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + mProfileType = message.arg2; + mPreferredContext = (int)message.obj; + Log.i(TAG, "Connecting " + contextTypeToString(message.arg1) + " to " + mDevice); + if (mAcmService.IsLockSupportAvailable(mDevice)) { + Log.d(TAG, "Exclusive Access support available, gatt should already be connected"); + //if lock support available then go for CSIP connect + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + //break; + } else { + Log.d(TAG, "Exclusive Access support not available, gatt should already be connected"); + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + } + if (!mAcmNativeInterface.connectAcm(mDevice, message.arg1, message.arg2, (int)message.obj)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice + " remain in connected"); + } + } break; + + case DISCONNECT: {//disconnect request goes individual + IsDisconnectRequested = true; + mIsDeviceWhitelisted = false; + mContextTypeToDisconnect = (int)message.obj; + IState state = mDisconnecting; + boolean disconnected_flag = false; + //check if disconnect is for individual context type + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + disconnected_flag = true; + } + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (mContextTypeToDisconnect != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connected state but broadcast the connection state*/ + state = mConnected; + } + } + StreamAudioService service = StreamAudioService.getStreamAudioService(); + processTransitionContextState(mConnected, (disconnected_flag ? mDisconnected : mDisconnecting), mContextTypeToDisconnect); + if (state == mConnected) { + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } else { + transitionTo(state); + } + /*mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null); // TODO: UUID ? + List members = new ArrayList(); + members.add(mDevice); + //setlockvalue takes device list + mCsipLockRequested = true; + mDeviceLocked = false; + mSetCoordinator.setLockValue(mAcmService.mCsipAppId, mSetId, members, BluetoothCsip.LOCK);*/ + } break; + + case CSIP_LOCK_STATUS_LOCKED: { + mCsipLockRequested = false; + int value = (int)message.arg1; + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + Log.d(TAG, "Exclusive Access state changed:" + value); + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (IsDisconnectRequested) { + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + } + transitionTo(mDisconnecting); + } else if (IsReconfigRequested) { + Log.w(TAG, "Reconfig requested Exclusive Access"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "reconfig error " + mDevice); + break; + } + } + } + } break; + + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_DISCONNECTED) + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + break; + + case CSIP_LOCK_RELEASE_TIMEOUT: + //lock release individual ? + Log.d(TAG, "Exclusive Access timeout to " + mDevice); + List devices = new ArrayList(); + devices.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, devices, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_RELEASED: + // ignore disconnect CSIP + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + mDeviceLocked = false; + break; + + case CODEC_CONFIG_CHANGED: { + IsReconfigRequested = true; + mReconfig = mAcmService.getAcmName(); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null ); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case START_STREAM: { + int value = (int)message.obj; + if (!mAcmNativeInterface.startStream(mDevice, value)) { + Log.e(TAG, "start stream error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null ); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case START_STREAM_REQ: { + if (!mAcmService.isPeerDeviceStreamingMusic(mDevice, mSetId)) { + mAcmService.StartStream(mGroupAddress, CONTEXT_TYPE_VOICE); + } + } break; + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Connected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + processAudioStateEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + default: + Log.e(TAG, "Connected: ignoring stack event: " + event); + break; + } + break; + + case GATT_CONNECTION_STATE_CHANGED: { + Log.e(TAG, "Connection state as disconnected " + mDevice); + removeMessages(GATT_CONNECTION_TIMEOUT); + int st = (int)message.obj; + if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, " GATT Disconnected"); + mIsDeviceWhitelisted = false; + mIsUpdateProfDisConnection = true; + transitionTo(mDisconnected); + } break; + } + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connected state + private void processConnectionEvent(int event, int contextType) { + IState smState; + switch (event) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + mCurrentContextType -= contextType; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + processTransitionContextState(mDisconnecting, mDisconnected, contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + //TODO:sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + Log.w(TAG, "ACM CONNECTED event for device: " + mDevice + " context type: " + contextTypeToString(contextType)); + if (contextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTING: { + Log.w(TAG, "Ignore ACM CONNECTED event: " + mDevice); + } break; + + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: { + if ((contextType == CONTEXT_TYPE_MUSIC) && + (mMusicConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for media - already disconnecting"); + } else if ((contextType == CONTEXT_TYPE_VOICE) && + (mVoiceConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for voice - already disconnecting"); + } else { + smState = mDisconnecting; + Log.w(TAG, "Connected device disconnecting: " + mDevice); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (contextType != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connecting state but broadcast the connection state*/ + smState = mConnected; + } + } + processTransitionContextState(mConnected, mDisconnecting, contextType); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (smState == mConnected) { + if (contextType == CONTEXT_TYPE_MUSIC) { + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else if (contextType == CONTEXT_TYPE_VOICE) { + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } + } else { + transitionTo(smState); + } + } + } break; + + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); + break; + } + } + + + // in Connected state + private void processAudioStateEvent(int state, int contextType) { + Log.i(TAG, "Connected: processAudioStateEvent: state: " + state + " mIsMusicPlaying: " + mIsMusicPlaying); + switch (state) { + case AcmStackEvent.AUDIO_STATE_STARTED: { + if (contextType == CONTEXT_TYPE_MUSIC) + mIsMusicPlaying = true; + else if (contextType == CONTEXT_TYPE_VOICE) + mIsVoicePlaying = true; + transitionTo(mStreaming); + } break; + case AcmStackEvent.AUDIO_STATE_REMOTE_SUSPEND: + case AcmStackEvent.AUDIO_STATE_STOPPED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + synchronized (this) { + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mIsMusicPlaying) { + Log.i(TAG, "Connected: stopped media playing: " + mDevice); + mIsMusicPlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (mIsVoicePlaying) { + Log.i(TAG, "Connected: stopped voice playing: " + mDevice); + mIsVoicePlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + } + } break; + default: + Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state); + break; + } + } + } + + + @VisibleForTesting + class Streaming extends State { + @Override + public void enter() { + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Streaming(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED)) { + removeDeferredMessages(CONNECT); + } + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mIsMusicPlaying) { + Log.i(TAG, "start playing media: " + mDevice); + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_PLAYING, ApmConst.AudioFeatures.MEDIA_AUDIO); + mAcmService.updateLeaChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice); + } else if (mIsVoicePlaying) { + Log.i(TAG, "start playing voice: " + mDevice); + service.onStreamStateChange(mDevice, BluetoothHeadset.STATE_AUDIO_CONNECTED, ApmConst.AudioFeatures.CALL_AUDIO); + setVoiceParameters(); + setCallAudioOn(true); + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Streaming(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mIsMusicPlaying) + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.MEDIA_AUDIO); + else if (mIsVoicePlaying) { + service.onStreamStateChange(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, ApmConst.AudioFeatures.CALL_AUDIO); + setCallAudioOn(false); + } + mIsMusicPlaying = false; + mIsVoicePlaying = false; + } + + @Override + public boolean processMessage(Message message) { + log("Streaming process message(" + mDevice + "): " + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + if (message.arg1 == CONTEXT_TYPE_MUSIC && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_MUSIC_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED + && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + mCurrentContextType += message.arg1; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + mProfileType = message.arg2; + mPreferredContext = (int)message.obj; + Log.i(TAG, "Connecting " + contextTypeToString(message.arg1) + " to " + mDevice); + if (mAcmService.IsLockSupportAvailable(mDevice)) { + Log.d(TAG, "Exclusive Access support available, gatt should already be connected"); + //if lock support available then go for CSIP connect + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + //break; + } else { + Log.d(TAG, "Exclusive Access support not available, gatt should already be connected"); + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + } + if (!mAcmNativeInterface.connectAcm(mDevice, message.arg1, message.arg2, (int)message.obj)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice + " remain in streaming"); + } + } break; + + case DISCONNECT: {//disconnect request goes individual + IsDisconnectRequested = true; + mIsDeviceWhitelisted = false; + mContextTypeToDisconnect = (int)message.obj; + IState state = mDisconnecting; + boolean disconnected_flag = false; + //check if disconnect is for individual context type + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + disconnected_flag = true; + } + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) + && (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (mContextTypeToDisconnect != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connected state but broadcast the connection state*/ + state = mConnected; + } + } + StreamAudioService service = StreamAudioService.getStreamAudioService(); + processTransitionContextState(mConnected, (disconnected_flag ? mDisconnected : mDisconnecting), mContextTypeToDisconnect); + if (state == mConnected) { + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + transitionTo(state); + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } else { + transitionTo(state); + } + /*mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null); // TODO: UUID ? + List members = new ArrayList(); + members.add(mDevice); + //setlockvalue takes device list + mCsipLockRequested = true; + mDeviceLocked = false; + mSetCoordinator.setLockValue(mAcmService.mCsipAppId, mSetId, members, BluetoothCsip.LOCK);*/ + } break; + + case CSIP_LOCK_STATUS_LOCKED: { + mCsipLockRequested = false; + int value = (int)message.arg1; + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + Log.d(TAG, "Exclusive Access state changed:" + value); + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (IsDisconnectRequested) { + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + } + transitionTo(mDisconnecting); + } else if (IsReconfigRequested) { + Log.w(TAG, "Reconfig requested Exclusive Access"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "reconfig error " + mDevice); + break; + } + } + } + } break; + + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_DISCONNECTED) + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + break; + + case CSIP_LOCK_RELEASE_TIMEOUT: + //lock release individual ? + Log.d(TAG, "Exclusive Access timeout to " + mDevice); + List devices = new ArrayList(); + devices.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, devices, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_RELEASED: + // ignore disconnect CSIP + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + mDeviceLocked = false; + break; + + case CODEC_CONFIG_CHANGED: { + IsReconfigRequested = true; + mReconfig = mAcmService.getAcmName(); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device is already acquired and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(setId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(setId, + members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case STOP_STREAM: { + int value = (int)message.obj; + if (!mAcmNativeInterface.stopStream(mDevice, value)) { + Log.e(TAG, "start stream error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null ); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case START_STREAM: + int value = (int)message.obj; + if (value == CONTEXT_TYPE_VOICE && mIsMusicPlaying) { + deferMessage(obtainMessage(START_STREAM_REQ, value)); + Log.wtf(TAG, "Defer START request for voice context"); + } + break; + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Streaming: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + processAudioStateEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + default: + Log.e(TAG, "Streaming: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Streaming state + private void processConnectionEvent(int event, int contextType) { + IState smState; + switch (event) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + //TODO: sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + Log.w(TAG, "Streaming device disconnected: " + mDevice); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + mCurrentContextType -= contextType; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + processTransitionContextState(mDisconnecting, mDisconnected, contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + transitionTo(mConnected); + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + //TODO:sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + Log.w(TAG, "ACM CONNECTED event for device: " + mDevice + " context type: " + contextTypeToString(contextType)); + if (contextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTING: { + Log.w(TAG, "ACM CONNECTING event: " + mDevice); + } break; + + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: { + if ((contextType == CONTEXT_TYPE_MUSIC) && + (mMusicConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for media - already disconnecting"); + } else if ((contextType == CONTEXT_TYPE_VOICE) && + (mVoiceConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for voice - already disconnecting"); + } else { + smState = mDisconnecting; + Log.w(TAG, "Connected device disconnecting: " + mDevice); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (contextType != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connecting state but broadcast the connection state*/ + smState = mConnected; + } + } + processTransitionContextState(mConnected, mDisconnecting, contextType); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (smState == mConnected) { + if (contextType == CONTEXT_TYPE_MUSIC) { + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else if (contextType == CONTEXT_TYPE_VOICE) { + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } + } else { + transitionTo(smState); + } + } + } break; + + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); + break; + } + } + + // in Streaming state + private void processAudioStateEvent(int state, int contextType) { + Log.i(TAG, "Streaming: processAudioStateEvent: state: " + state + "mIsMusicPlaying: " + mIsMusicPlaying); + switch (state) { + case AcmStackEvent.AUDIO_STATE_STARTED: + Log.i(TAG, "Streaming: already started: " + mDevice); + break; + case AcmStackEvent.AUDIO_STATE_REMOTE_SUSPEND: + case AcmStackEvent.AUDIO_STATE_STOPPED: + synchronized (this) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mIsMusicPlaying) { + Log.i(TAG, "Streaming: stopped media playing: " + mDevice); + mIsMusicPlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + if (mAcmService.isShoPendingStop()) { + Log.i(TAG, "Streaming: SHO was pending earlier, complete now"); + mAcmService.resetShoPendingStop(); + service.onActiveDeviceChange(null, ApmConst.AudioFeatures.MEDIA_AUDIO); + } + transitionTo(mConnected); + } + } + if (contextType == CONTEXT_TYPE_VOICE) { + if (mIsVoicePlaying) { + Log.i(TAG, "Streaming: stopped voice playing: " + mDevice); + mIsVoicePlaying = false; + service.onStreamStateChange(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, ApmConst.AudioFeatures.CALL_AUDIO); + setCallAudioOn(false); + if (mAcmService.isVoiceShoPendingStop()) { + Log.i(TAG, "Voice SHO was pending earlier, complete now"); + mAcmService.resetVoiceShoPendingStop(); + service.onActiveDeviceChange(mAcmService.getVoiceActiveDevice(), ApmConst.AudioFeatures.CALL_AUDIO); + } + transitionTo(mConnected); + } + } + } + break; + default: + Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state); + break; + } + } + } + + private String getFrameDuration() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs1 = mCodecConfig.getCodecSpecific1() >> 32 & 0xff; + if (cs1 == 0x00) { + return "7.5"; + } else { + return "10"; + } + } + + private String getLc3BlocksPerSdu() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs1 = mCodecConfig.getCodecSpecific1() >> 40 & 0xff; + return Integer.toString((int)cs1); + } + + private String getCodectype() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs3 = (mCodecConfig.getCodecSpecific3() >> + (CS_PARAM_NUM_BITS * CS_PARAM_1ST_INDEX)) & CS_PARAM_IND_MASK; + if (cs3 == CODEC_TYPE_LC3Q) { + return "LC3Q"; + } else { + return "LC3"; + } + } + + private String getLc3qValues() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs3 = mCodecConfig.getCodecSpecific3(); + long cs4 = mCodecConfig.getCodecSpecific4(); + + String vsMetaDataLc3qVal = String.join(",", new String[]{ + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_8TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_7TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_6TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_5TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_4TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_3RD_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_2ND_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_1ST_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_8TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", (((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_2ND_INDEX)) & CS_PARAM_IND_MASK) & 0x01)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_1ST_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_7TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_6TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_5TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_4TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_3RD_INDEX)) & CS_PARAM_IND_MASK)) + }); + Log.i(TAG, "getLc3qValues() for " + mDevice + ": " + vsMetaDataLc3qVal); + return vsMetaDataLc3qVal; + } + + private String getRxTxConfigIndex() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + int sampleRate = mCodecConfig.getSampleRate(); + long cs1 = mCodecConfig.getCodecSpecific1() >> 0 & 0xff; + if (sampleRate == BluetoothCodecConfig.SAMPLE_RATE_8000) { + if (cs1 == 0x01) { + return "0"; + } else { + return "1"; + } + } else if (sampleRate == BluetoothCodecConfig.SAMPLE_RATE_16000) { + if (cs1 == 0x01) { + return "2"; + } else { + return "3"; + } + } else if (sampleRate == BluetoothCodecConfig.SAMPLE_RATE_32000) { + if (cs1 == 0x01) { + return "4"; + } else { + return "5"; + } + } + return "0"; + } + + private void setVoiceParameters() { + String keyValuePairs = String.join(";", new String[]{ + CODEC_NAME + "=" + "LC3", + STREAM_MAP + "=" + "(0, 0, M, 0, 1, L),(1, 0, M, 1, 1, R)", + FRAME_DURATION + "=" + getFrameDuration(), + SDU_BLOCK + "=" + getLc3BlocksPerSdu(), + RXCONFIG_INDX + "=" + getRxTxConfigIndex(), + TXCONFIG_INDX + "=" + getRxTxConfigIndex(), + VERSION + "=" + "21", + VENDOR_META_DATA + "=" + getLc3qValues() + }); + Log.i(TAG, "setVoiceParameters for " + mDevice + ": " + keyValuePairs); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.setCallAudioParam(keyValuePairs); + } + + private void setCallAudioOn(boolean on) { + Log.i(TAG, "set Call Audio On: " + on); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.setCallAudioOn(on); + } + + int getConnectionState() { + return mConnectionState; + } + + int getCsipConnectionState() { + return mCsipConnectionState; + } + + BluetoothDevice getDevice() { + return mDevice; + } + + BluetoothDevice getPeerDevice() { + BluetoothDevice d = null; + List members = mAcmService.getCsipManager().getSetMembers(mSetId); + if (members == null) { + Log.d(TAG, "No set member found"); + return d; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + d = i.next(); + if (!(Objects.equals(d, mDevice))) { + Log.d(TAG, "Device: " + d); + break; + } + } + } + return d; + } + + void removeDevicefromBgWL() { + Log.d(TAG, "remove device from BG WL"); + if (mBluetoothGatt != null && mIsDeviceWhitelisted) { + mIsDeviceWhitelisted = false; + mBluetoothGatt.disconnect(); + } + } + + boolean isConnected() { + synchronized (this) { + return (getConnectionState() == BluetoothProfile.STATE_CONNECTED); + } + } + + boolean isCsipLockRequested() { + synchronized (this) { + return mCsipLockRequested; + } + } + + boolean isMusicPlaying() { + synchronized (this) { + return mIsMusicPlaying; + } + } + + boolean isVoicePlaying() { + synchronized (this) { + return mIsVoicePlaying; + } + } + + private void processTransitionContextState(IState prevState, IState nextState, int contextType) { + int pState = AcmStateToBluetoothProfileState(prevState); + int nState = AcmStateToBluetoothProfileState(nextState); + if (contextType == CONTEXT_TYPE_MUSIC) { + mLastMusicConnectionState = pState; + mMusicConnectionState = nState; + } else if (contextType == CONTEXT_TYPE_VOICE) { + mLastVoiceConnectionState = pState; + mVoiceConnectionState = nState; + } else if (contextType == CONTEXT_TYPE_MUSIC_VOICE) { + mLastMusicConnectionState = pState; + mLastVoiceConnectionState = pState; + mMusicConnectionState = nState; + mVoiceConnectionState = nState; + } + } + + // NOTE: This event is processed in any state + @VisibleForTesting + void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus, int contextType) { + Log.d(TAG,"ProcessCodecConfigEvent: context type :" + contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + BluetoothCodecConfig mCodecConfig = newCodecStatus.getCodecConfig(); + long cs3 = mCodecConfig.getCodecSpecific3(); + cs3 |= LE_AUDIO_AVAILABLE_LICENSED; + BluetoothCodecConfig mCodecConfigLc3 = new BluetoothCodecConfig( + mCodecConfig.getCodecType(), + mCodecConfig.getCodecPriority(), + mCodecConfig.getSampleRate(), + mCodecConfig.getBitsPerSample(), + mCodecConfig.getChannelMode(), + mCodecConfig.getCodecSpecific1(), mCodecConfig.getCodecSpecific2(), + cs3, mCodecConfig.getCodecSpecific4()); + mMusicCodecStatus = new BluetoothCodecStatus(mCodecConfigLc3, + newCodecStatus.getCodecsLocalCapabilities(), + newCodecStatus.getCodecsSelectableCapabilities()); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onMediaCodecConfigChange(mGroupAddress, mMusicCodecStatus, contextType); + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceCodecStatus = newCodecStatus; + } + } + + @Override + protected String getLogRecString(Message msg) { + StringBuilder builder = new StringBuilder(); + builder.append(messageWhatToString(msg.what)); + builder.append(": "); + builder.append("arg1=") + .append(msg.arg1) + .append(", arg2=") + .append(msg.arg2) + .append(", obj=") + .append(msg.obj); + return builder.toString(); + } + + private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus, + BluetoothCodecStatus newCodecStatus) { + if (prevCodecStatus == null) { + return false; + } + return BluetoothCodecStatus.sameCapabilities( + prevCodecStatus.getCodecsSelectableCapabilities(), + newCodecStatus.getCodecsSelectableCapabilities()); + } + + private static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case STACK_EVENT: + return "STACK_EVENT"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + default: + break; + } + return Integer.toString(what); + } + + private static String contextTypeToString(int contextType) { + switch (contextType) { + case CONTEXT_TYPE_UNKNOWN: + return "UNKNOWN"; + case CONTEXT_TYPE_MUSIC: + return "MEDIA"; + case CONTEXT_TYPE_VOICE: + return "CONVERSATIONAL"; + case CONTEXT_TYPE_MUSIC_VOICE: + return "MEDIA+CONVERSATIONAL"; + default: + break; + } + return Integer.toString(contextType); + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + /*private static String musicAudioStateToString(int state) { + switch (state) { + case BluetoothA2dp.STATE_PLAYING: + return "PLAYING"; + case BluetoothA2dp.STATE_NOT_PLAYING: + return "NOT_PLAYING"; + default: + break; + } + return Integer.toString(state); + }*/ + + private static String voiceAudioStateToString(int state) { + switch (state) { + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + return "AUDIO_DISCONNECTED"; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + return "AUDIO_CONNECTING"; + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + return "AUDIO_CONNECTED"; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTING: + return "AUDIO_DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + private static int AcmStateToBluetoothProfileState(IState state) { + if (state instanceof Disconnected) { + return BluetoothProfile.STATE_DISCONNECTED; + } else if (state instanceof Connecting) { + return BluetoothProfile.STATE_CONNECTING; + } else if (state instanceof Connected) { + return BluetoothProfile.STATE_CONNECTED; + } else if (state instanceof Disconnecting) { + return BluetoothProfile.STATE_DISCONNECTING; + } + Log.w(TAG, "Unknown State"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mDevice: " + mDevice); + ProfileService.println(sb, " StateMachine: " + this.toString()); + ProfileService.println(sb, " mIsMusicPlaying: " + mIsMusicPlaying); + synchronized (this) { + if (mVoiceCodecStatus != null) { + ProfileService.println(sb, " Voice mCodecConfig: " + mVoiceCodecStatus.getCodecConfig()); + } + if (mMusicCodecStatus != null) { + ProfileService.println(sb, " Music mCodecConfig: " + mMusicCodecStatus.getCodecConfig()); + } + } + // Dump the state machine logs + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + super.dump(new FileDescriptor(), printWriter, new String[]{}); + printWriter.flush(); + stringWriter.flush(); + ProfileService.println(sb, " StateMachineLog:"); + Scanner scanner = new Scanner(stringWriter.toString()); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + ProfileService.println(sb, " " + line); + } + scanner.close(); + } + + @Override + protected void log(String msg) { + if (DBG) { + super.log(msg); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java new file mode 100644 index 0000000000000000000000000000000000000000..dfcabf14f1ebc1d62f7b79b44c4d6281083d6b14 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java @@ -0,0 +1,1444 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +/** + * Bluetooth ActiveDeviceManagerService. There is one instance each for + * voice and media profile management.. + * - "Idle" and "Active" are steady states. + * - "Activating" and "Deactivating" are transient states until the + * SHO / Deactivation is completed. + * + * + * (Idle) + * | ^ + * SetActive | | Removed + * V | + * (Activating) (Deactivating) + * | ^ + * Activated | | setActive(NULL) / removeDevice + * V | + * (Active / Broadcasting) + * + * + */ + +package com.android.bluetooth.apm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothHeadset; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ActiveDeviceManager; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.a2dp.A2dpService; +import com.android.bluetooth.hearingaid.HearingAidService; +import com.android.bluetooth.hfp.HeadsetService; +import com.android.bluetooth.mcp.McpService; +import com.android.bluetooth.broadcast.BroadcastService; +import com.android.bluetooth.cc.CCService; +import android.content.Intent; +import android.content.Context; +import android.os.Looper; +import android.os.Message; +import android.os.HandlerThread; +import android.os.UserHandle; +import android.os.SystemProperties; +import android.util.Log; +import android.media.AudioManager; + +import com.android.bluetooth.BluetoothStatsLog; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.Boolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.lang.Integer; +import java.util.Scanner; +import java.util.Objects; + +public class ActiveDeviceManagerService { + private static final boolean DBG = true; + private static final String TAG = "APM: ActiveDeviceManagerService"; + private static ActiveDeviceManagerService sActiveDeviceManager = null; + private AudioManager mAudioManager; + private HandlerThread[] thread = new HandlerThread[AudioType.SIZE]; + private ShoStateMachine[] sm = new ShoStateMachine[AudioType.SIZE]; + private ApmNativeInterface apmNative; + private Context mContext; + private boolean txStreamSuspended = false; + private final Lock lock = new ReentrantLock(); + private final Condition mediaHandoffComplete = lock.newCondition(); + private final Condition voiceHandoffComplete = lock.newCondition(); + static class Event { + static final int SET_ACTIVE = 1; + static final int ACTIVE_DEVICE_CHANGE = 2; + static final int REMOVE_DEVICE = 3; + static final int DEVICE_REMOVED = 4; + static final int ACTIVATE_TIMEOUT = 5; + static final int DEACTIVATE_TIMEOUT = 6; + static final int SUSPEND_RECORDING = 7; + static final int RESUME_RECORDING = 8; + static final int RETRY_DEACTIVATE = 9; + static final int STOP_SM = 0; + } + + public static final int SHO_SUCCESS = 0; + public static final int SHO_PENDING = 1; + public static final int SHO_FAILED = 2; + public static final int ALREADY_ACTIVE = 3; + + static final int RETRY_LIMIT = 4; + static final int ACTIVATE_TIMEOUT_DELAY = 3000; + static final int DEACTIVATE_TIMEOUT_DELAY = 2000; + static final int DEACTIVATE_TRY_DELAY = 500; + + private ActiveDeviceManagerService (Context context) { + thread[AudioType.MEDIA] = new HandlerThread("ActiveDeviceManager.MediaThread"); + thread[AudioType.MEDIA].start(); + Looper mediaLooper = thread[AudioType.MEDIA].getLooper(); + sm[AudioType.MEDIA] = new ShoStateMachine(AudioType.MEDIA, mediaLooper); + + thread[AudioType.VOICE] = new HandlerThread("ActiveDeviceManager.VoiceThread"); + thread[AudioType.VOICE].start(); + Looper voiceLooper = thread[AudioType.VOICE].getLooper(); + sm[AudioType.VOICE] = new ShoStateMachine(AudioType.VOICE, voiceLooper); + + mContext = context; + apmNative = ApmNativeInterface.getInstance(); + apmNative.init(); + + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when ActiveDeviceManagerService starts"); + } + + public static ActiveDeviceManagerService get(Context context) { + if(sActiveDeviceManager == null) { + sActiveDeviceManager = new ActiveDeviceManagerService(context); + ActiveDeviceManagerServiceIntf.init(sActiveDeviceManager); + } + return sActiveDeviceManager; + } + + public static ActiveDeviceManagerService get() { + return sActiveDeviceManager; + } + + public boolean setActiveDevice(BluetoothDevice device, Integer mAudioType, Boolean isUIReq, Boolean playReq) { + Log.d(TAG, "setActiveDevice(" + device + ") audioType: " + mAudioType); + boolean isCallActive = false; + if(ApmConst.AudioFeatures.CALL_AUDIO == mAudioType) { + CallAudio mCallAudio = CallAudio.get(); + isCallActive = mCallAudio.isAudioOn(); + } + + synchronized(sm[mAudioType]) { + sm[mAudioType].mSHOQueue.device = device; + sm[mAudioType].mSHOQueue.isUIReq = isUIReq; + sm[mAudioType].mSHOQueue.PlayReq = (playReq || isCallActive); + sm[mAudioType].mSHOQueue.isBroadcast = false; + sm[mAudioType].mSHOQueue.isRecordingMode = false; + sm[mAudioType].mSHOQueue.isGamingMode = false; + } + + if(device != null) { + sm[mAudioType].sendMessage(Event.SET_ACTIVE); + } + else { + if (mAudioType == AudioType.MEDIA && sm[mAudioType].mState == sm[mAudioType].mBroadcasting) { + Log.d(TAG, "LE Broadcast is active, ignore REMOVE_DEVICE"); + } else { + sm[mAudioType].sendMessage(Event.REMOVE_DEVICE); + } + } + return isUIReq; + } + + public boolean setActiveDevice(BluetoothDevice device, Integer mAudioType, Boolean isUIReq) { + return setActiveDevice(device, mAudioType, isUIReq, false); + } + + public boolean setActiveDevice(BluetoothDevice device, Integer mAudioType) { + return setActiveDevice(device, mAudioType, false, false); + } + + public boolean setActiveDeviceBlocking(BluetoothDevice device, Integer mAudioType) { + Log.d(TAG, "setActiveDeviceBlocking: Enter"); + if(ApmConst.AudioFeatures.CALL_AUDIO == mAudioType) { + setActiveDevice(device, mAudioType, false, false); + try { + lock.lock(); + voiceHandoffComplete.await(); + } catch (InterruptedException e) { + Log.d(TAG, "setActiveDeviceBlocking: Unblocked because of exception: " + e); + } finally { + Log.d(TAG, "setActiveDeviceBlocking: unlock"); + lock.unlock(); + } + } + Log.d(TAG, "setActiveDeviceBlocking: Exit"); + return true; + } + + public BluetoothDevice getQueuedDevice(Integer mAudioType) { + return sm[mAudioType].mSHOQueue.device; + } + + public boolean removeActiveDevice(Integer mAudioType, Boolean forceStopAudio) { + sm[mAudioType].mSHOQueue.forceStopAudio = forceStopAudio; + setActiveDevice(null, mAudioType, false); + return true; + } + + public BluetoothDevice getActiveDevice(Integer mAudioType) { + return sm[mAudioType].Current.Device; + } + + public int getActiveProfile(Integer mAudioType) { + if(sm[mAudioType].mState == sm[mAudioType].mBroadcasting) { + // Use Current.Profile here + return ApmConst.AudioProfiles.BROADCAST_LE; + } else if(sm[mAudioType].mState == sm[mAudioType].mActive) { + return sm[mAudioType].Current.Profile; + } + return ApmConst.AudioProfiles.NONE; + } + + public boolean onActiveDeviceChange(BluetoothDevice device, Integer mAudioType) { + return onActiveDeviceChange(device, mAudioType, ApmConst.AudioProfiles.NONE); + } + + public boolean onActiveDeviceChange(BluetoothDevice device, Integer mAudioType, Integer mProfile) { + if (device != null || mProfile == ApmConst.AudioProfiles.BROADCAST_LE) { + DeviceProfileCombo mDeviceProfileCombo = new DeviceProfileCombo(device, mProfile); + sm[mAudioType].sendMessage(Event.ACTIVE_DEVICE_CHANGE, mDeviceProfileCombo); + } + else + sm[mAudioType].sendMessage(Event.DEVICE_REMOVED); + return true; + } + + public boolean enableBroadcast(BluetoothDevice device) { + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = true; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean disableBroadcast() { + Log.d(TAG, "disableBroadcast"); + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = false; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + if (sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mBroadcasting) { + sm[AudioType.MEDIA].sendMessage(Event.REMOVE_DEVICE); + } + return true; + } + + public boolean enableGaming(BluetoothDevice device) { + if (sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mGamingMode && + device.equals(getActiveDevice(AudioType.MEDIA))) { + Log.d(TAG, "Device already in Gaming Mode"); + return true; + } + + Log.d(TAG, "enableGaming"); + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = false; + sm[AudioType.MEDIA].mSHOQueue.isGamingMode = true; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean disableGaming(BluetoothDevice device) { + if (sm[AudioType.MEDIA].mState != sm[AudioType.MEDIA].mGamingMode) { + Log.e(TAG, "Gaming Mode not active"); + return true; + } + + /*MediaAudio mMediaAudio = MediaAudio.get(); + if(mMediaAudio != null && mMediaAudio.isA2dpPlaying(device)) { + Log.w(TAG, "Gaming Stream is Active"); + return false; + }*/ + + Log.d(TAG, "disableGaming"); + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isGamingMode = false; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + sm[AudioType.MEDIA].mSHOQueue.isUIReq = true; + } + + //sm[AudioType.MEDIA].sendMessageDelayed(Event.SET_ACTIVE, DEACTIVATE_TRY_DELAY); + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean enableRecording(BluetoothDevice device) { + Log.d(TAG, "enableRecording: " + device); + + MediaAudio mMediaAudio = MediaAudio.get(); + if(txStreamSuspended == false) { + Log.d(TAG, "Set A2dpSuspended=true"); + mAudioManager.setParameters("A2dpSuspended=true"); + txStreamSuspended = true; + } + + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = false; + sm[AudioType.MEDIA].mSHOQueue.isGamingMode = false; + sm[AudioType.MEDIA].mSHOQueue.isRecordingMode = true; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + sm[AudioType.MEDIA].mSHOQueue.isUIReq = true; + } + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean disableRecording(BluetoothDevice device) { + Log.d(TAG, "disableRecording: " + device); + + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isRecordingMode = false; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + + if (sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mRecordingMode) { + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + } + return true; + } + + public boolean suspendRecording(Boolean suspend) { + Log.d(TAG, "suspendRecording: " + suspend); + + if(sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mRecordingMode) { + if(suspend) { + sm[AudioType.MEDIA].sendMessage(Event.SUSPEND_RECORDING); + } else { + sm[AudioType.MEDIA].sendMessage(Event.RESUME_RECORDING); + } + } + return true; + } + + public boolean isRecordingActive(BluetoothDevice device) { + Log.d(TAG, "isRecordingActive"); + return sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mRecordingMode; + } + + public boolean isStableState(int mAudioType) { + State state = sm[mAudioType].mState; + return !(sm[mAudioType].mActivating == state || sm[mAudioType].mDeactivating == state); + } + + private void broadcastActiveDeviceChange(BluetoothDevice device, int mAudioType) { + if (DBG) { + Log.d(TAG, "broadcastActiveDeviceChange(" + device + ")"); + } + Intent intent; + + /*if (mAdapterService != null) + BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP, + mAdapterService.obfuscateAddress(device), 0);*/ + + if(mAudioType == AudioType.MEDIA) + intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); + else + intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + if(mAudioType == AudioType.MEDIA) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if(mA2dpService == null) { + Log.e(TAG, "A2dp Service not ready"); + return; + } + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT); + } else { + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService == null) { + Log.e(TAG, "Headset Service not ready"); + return; + } + mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + public void disable() { + Log.d(TAG, "disable() called"); + sm[AudioType.MEDIA].sendMessage(Event.STOP_SM); + sm[AudioType.VOICE].sendMessage(Event.STOP_SM); + } + + public void cleanup() { + sm[AudioType.VOICE].doQuit(); + sm[AudioType.MEDIA].doQuit(); + thread[AudioType.VOICE].quitSafely(); + thread[AudioType.MEDIA].quitSafely(); + thread[AudioType.VOICE] = null; + thread[AudioType.MEDIA] = null; + sActiveDeviceManager = null; + } + + private class DeviceProfileCombo { + BluetoothDevice Device; + BluetoothDevice absoluteDevice; + int Profile; + + DeviceProfileCombo(BluetoothDevice mDevice, int mProfile) { + Device = mDevice; + Profile = mProfile; + } + + DeviceProfileCombo() { + Device = null; + Profile = ApmConst.AudioProfiles.NONE; + } + } + + private final class ShoStateMachine extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "APM: ActiveDeviceManagerService"; + + static final int IDLE = 0; + static final int ACTIVATING = 1; + static final int ACTIVE = 2; + static final int DEACTIVATING = 3; + static final int BROADCAST_ACTIVE = 4; + static final int GAMING_ACTIVE = 5; + static final int RECORDING_ACTIVE = 6; + + private Idle mIdle; + private Activating mActivating; + private Active mActive; + private Deactivating mDeactivating; + private Broadcasting mBroadcasting; + private Gaming mGamingMode; + private Recording mRecordingMode; + + private DeviceProfileCombo Current; + private DeviceProfileCombo Target; + private SHOReq mTargetSHO; + private SHOReq mSHOQueue; + + private DeviceProfileMap dpm; + + private int mAudioType; + private State mState; + private State mPrevState = null; + private BluetoothDevice mPrevActiveDevice; + private int mPrevActiveProfile = ApmConst.AudioProfiles.NONE; + boolean enabled; + boolean updatePending = false; + boolean mRecordingSuspended = false; + private String sAudioType; + + ShoStateMachine (int audioType, Looper looper) { + super(TAG, looper); + setDbg(DBG); + + mIdle = new Idle(); + mActivating = new Activating(); + mActive = new Active(); + mDeactivating = new Deactivating(); + mBroadcasting = new Broadcasting(); + mGamingMode = new Gaming(); + mRecordingMode = new Recording(); + + Current = new DeviceProfileCombo(); + Target = new DeviceProfileCombo(); + mSHOQueue = new SHOReq(); + mTargetSHO = new SHOReq(); + + addState(mIdle); + addState(mActivating); + addState(mActive); + addState(mDeactivating); + addState(mBroadcasting); + addState(mGamingMode); + addState(mRecordingMode); + + mAudioType = audioType; + if(mAudioType == AudioType.MEDIA) + sAudioType = new String("MEDIA"); + else if(mAudioType == AudioType.VOICE) + sAudioType = new String("VOICE"); + + enabled = true; + setInitialState(mIdle); + start(); + } + + public void doQuit () { + Log.i(TAG, "Stopping SHO StateMachine for " + mAudioType); + sAudioType = null; + quitNow(); + } + + /* public void cleanUp { + + }*/ + + private String messageWhatToString(int msg) { + switch (msg) { + case Event.SET_ACTIVE: + return "SET ACTIVE"; + case Event.ACTIVE_DEVICE_CHANGE: + return "ACTIVE DEVICE CHANGED"; + case Event.REMOVE_DEVICE: + return "REMOVE DEVICE"; + case Event.DEVICE_REMOVED: + return "REMOVED"; + case Event.ACTIVATE_TIMEOUT: + return "SET ACTIVE TIMEOUT"; + case Event.DEACTIVATE_TIMEOUT: + return "REMOVE DEVICE TIMEOUT"; + case Event.STOP_SM: + return "STOP STATE MACHINE"; + default: + break; + } + return Integer.toString(msg); + } + + int startSho(BluetoothDevice device, int profile) { + MediaAudio mMediaAudio = MediaAudio.get(); + int ret = SHO_FAILED; + StreamAudioService streamAudioService; + Log.e(TAG, ": startSho() for device: " + device + ", for profile: " + profile); + switch (profile) { + case ApmConst.AudioProfiles.A2DP: + A2dpService a2dpService = A2dpService.getA2dpService(); + if(a2dpService != null) + // pass play status here + ret = a2dpService.setActiveDevice(device, false); + break; + case ApmConst.AudioProfiles.HFP: + HeadsetService headsetService = HeadsetService.getHeadsetService(); + if(headsetService != null) + ret = headsetService.setActiveDeviceHF(device); + break; + case ApmConst.AudioProfiles.TMAP_MEDIA: + case ApmConst.AudioProfiles.BAP_MEDIA: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, ApmConst.AudioProfiles.BAP_MEDIA, false); + if (ret == ActiveDeviceManagerService.ALREADY_ACTIVE) { + ret = SHO_SUCCESS; + } + break; + case ApmConst.AudioProfiles.BAP_RECORDING: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, ApmConst.AudioProfiles.BAP_RECORDING, false); + if (ret == ActiveDeviceManagerService.ALREADY_ACTIVE) { + ret = SHO_SUCCESS; + } + break; + case ApmConst.AudioProfiles.TMAP_CALL: + case ApmConst.AudioProfiles.BAP_CALL: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, profile, false); + break; + case ApmConst.AudioProfiles.BAP_GCP: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, ApmConst.AudioProfiles.BAP_GCP, false); + if (ret == ActiveDeviceManagerService.ALREADY_ACTIVE) { + ret = SHO_SUCCESS; + } + break; + case ApmConst.AudioProfiles.BROADCAST_LE: + //ret = SHO_SUCCESS;//broadcastService.setActiveDevice(); + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if (mBroadcastService != null) + ret = mBroadcastService.setActiveDevice(device); + break; + case ApmConst.AudioProfiles.HAP_BREDR: + HearingAidService hearingAidService = HearingAidService.getHearingAidService(); + ret = hearingAidService.setActiveDevice(device) ? SHO_SUCCESS : SHO_FAILED; + break; + } + return ret; + } + + class Idle extends State { + @Override + public void enter() { + synchronized (this) { + mState = mIdle; + } + Current.Device = null; + //2 Update dependent profiles + if(mPrevState != null && mPrevActiveDevice != null) { + broadcastActiveDeviceChange (null, mAudioType); + + if (mAudioType == AudioType.MEDIA && + mPrevActiveProfile != ApmConst.AudioProfiles.HAP_BREDR) { + mPrevActiveProfile = ApmConst.AudioProfiles.NONE; + MediaAudio mMediaAudio = MediaAudio.get(); + boolean suppressNoisyIntent = !mTargetSHO.forceStopAudio + && (mMediaAudio.getConnectionState(mPrevActiveDevice) + == BluetoothProfile.STATE_CONNECTED); + /*TODO: Add profile check here*/ + if(mAudioManager != null) { + log("De-Activate Device " + mPrevActiveDevice + " Noisy Intent: " + suppressNoisyIntent); + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + mPrevActiveDevice, BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.A2DP, suppressNoisyIntent, -1); + } + } + + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, mAudioType); + } + } + + if(txStreamSuspended && mAudioType == AudioType.MEDIA) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } + + if(!enabled) + log("state machine stopped"); + } + + @Override + public void exit() { + mPrevState = mIdle; + mPrevActiveDevice = null; + } + + @Override + public boolean processMessage(Message message) { + log("Idle: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + if(!enabled) { + log("State Machine not running. Returning"); + return NOT_HANDLED; + } + + switch(message.what) { + case Event.SET_ACTIVE: + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + /* Might move to active here*/ + case Event.REMOVE_DEVICE: + log("Idle: Process Message Ignored"); + break; + case Event.STOP_SM: + enabled = false; + log("state machine stopped"); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Activating extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mActivating; + updatePending = true; + + Target.Device = mSHOQueue.device; + Target.absoluteDevice = Target.Device; + mTargetSHO.copy(mSHOQueue); + mSHOQueue.reset(); + Log.w(TAG, "Activating " + sAudioType + " Device: " + Target.Device); + } + + dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + if (mTargetSHO.isBroadcast) { + Target.Profile = dpm.getProfile(Target.Device, ApmConst.AudioFeatures.BROADCAST_AUDIO); + mSHOQueue.device = Current.Device; + mSHOQueue.isUIReq = false; + } else if (mTargetSHO.isRecordingMode) { + mSHOQueue.device = Current.Device; + Target.Profile = ApmConst.AudioProfiles.BAP_RECORDING; + mSHOQueue.isUIReq = false; + } else if (mTargetSHO.isGamingMode) { + /*Only single profile supports gaming Mode*/ + Target.Profile = ApmConst.AudioProfiles.BAP_GCP; + } else { + Target.Profile = dpm.getProfile(Target.Device, mAudioType); + } + + if(Target.Profile == ApmConst.AudioProfiles.BAP_CALL || + Target.Profile == ApmConst.AudioProfiles.BAP_MEDIA || + Target.Profile == ApmConst.AudioProfiles.BAP_RECORDING || + Target.Profile == ApmConst.AudioProfiles.BAP_GCP) { + StreamAudioService streamAudioService = StreamAudioService.getStreamAudioService(); + Target.Device = streamAudioService.getDeviceGroup(Target.Device); + } + + if(Target.Device == null) { + Log.e(TAG, "Target Device is null, Returning"); + transitionTo(mPrevState); + updatePending = false; + return; + } + + if(Target.Device.equals(Current.Device) && Target.Profile == Current.Profile){ + Log.d(TAG,"Target Device: " + Target.Device + " and Profile: " + Target.Profile + + " already active"); + transitionTo(mPrevState); + updatePending = false; + return; + } + + if(Current.Device == null || isSameProfile(Current.Profile, Target.Profile, mAudioType)) { + /* Single Step SHO*/ + ActivateDevice(Target, mTargetSHO); + } else { + /*Multi Step SHO*/ + ret = startSho(null, Current.Profile); + if(SHO_PENDING == ret) { + sendMessageDelayed(Event.DEACTIVATE_TIMEOUT, DEACTIVATE_TIMEOUT_DELAY); + } else if(ret == SHO_FAILED) { + mTargetSHO.retryCount = 1; + sendMessageDelayed(Event.RETRY_DEACTIVATE, DEACTIVATE_TRY_DELAY); + } else if(SHO_SUCCESS == ret) { + mPrevState = mIdle; + Current.Device = null; + ActivateDevice(Target, mTargetSHO); + } + } + } + + @Override + public void exit() { + removeMessages(Event.ACTIVATE_TIMEOUT); + mPrevState = mActivating; + } + + @Override + public boolean processMessage(Message message) { + log("Activating: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + log("New SHO request while handling previous. Add to queue"); + removeDeferredMessages(Event.REMOVE_DEVICE); + removeDeferredMessages(Event.SET_ACTIVE); + deferMessage(message); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + DeviceProfileCombo mDeviceProfileCombo = (DeviceProfileCombo)message.obj; + removeMessages(Event.ACTIVATE_TIMEOUT); + if (Target.Profile == ApmConst.AudioProfiles.BAP_GCP + && Target.Profile == mDeviceProfileCombo.Profile) { + Current.Device = Target.Device; + Current.Profile = Target.Profile; + transitionTo(mGamingMode); + } else if (Target.Profile == ApmConst.AudioProfiles.BROADCAST_LE + && Target.Profile == mDeviceProfileCombo.Profile) { + Current.Device = Target.Device; + Current.Profile = Target.Profile; + transitionTo(mBroadcasting); + } else if(Target.Device != null && Target.Device.equals(mDeviceProfileCombo.Device)) { + Current.Device = mDeviceProfileCombo.Device; + Current.Profile = Target.Profile; + Current.absoluteDevice = Target.absoluteDevice; + transitionTo(mActive); + } + break; + + case Event.REMOVE_DEVICE: + removeDeferredMessages(Event.REMOVE_DEVICE); + deferMessage(message); + break; + + case Event.DEVICE_REMOVED: + mPrevState = mIdle; + Current.Device = null; + removeMessages(Event.DEACTIVATE_TIMEOUT); + ActivateDevice(Target, mTargetSHO); + break; + + case Event.RETRY_DEACTIVATE: + ret = startSho(null, Current.Profile); + if(SHO_PENDING == ret) { + mTargetSHO.retryCount = 0; + sendMessageDelayed(Event.DEACTIVATE_TIMEOUT, DEACTIVATE_TIMEOUT_DELAY); + } else if(ret == SHO_FAILED) { + if(mTargetSHO.retryCount >= RETRY_LIMIT) { + updatePending = false; + transitionTo(mPrevState); + } else { + mTargetSHO.retryCount++; + sendMessageDelayed(Event.RETRY_DEACTIVATE, DEACTIVATE_TRY_DELAY); + } + } else if(SHO_SUCCESS == ret) { + mTargetSHO.retryCount = 0; + mPrevState = mIdle; + Current.Device = null; + ActivateDevice(Target, mTargetSHO); + } + break; + + case Event.ACTIVATE_TIMEOUT: + case Event.DEACTIVATE_TIMEOUT: + transitionTo(mPrevState); + break; + + case Event.STOP_SM: + deferMessage(message); + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + void ActivateDevice(DeviceProfileCombo mTarget, SHOReq mTargetSHO) { + ret = startSho(mTarget.Device, mTarget.Profile); + if(SHO_PENDING == ret) { + Current.Device = mTarget.Device; + Current.absoluteDevice = mTarget.absoluteDevice; + Current.Profile = mTarget.Profile; + if(mAudioType == AudioType.MEDIA) { + sendActiveDeviceMediaUpdate(Current); + } + sendMessageDelayed(Event.ACTIVATE_TIMEOUT, ACTIVATE_TIMEOUT_DELAY); + } else if(ret == SHO_FAILED) { + if (mState == mBroadcasting) { + mTargetSHO.forceStopAudio = true; + Log.d(TAG,"Previous state was broadcasting, moving to idle"); + } + updatePending = false; + transitionTo(mPrevState); + } else if(SHO_SUCCESS == ret) { + Current.Device = mTarget.Device; + Current.absoluteDevice = mTarget.absoluteDevice; + Current.Profile = mTarget.Profile; + if(mTargetSHO.isBroadcast) { + transitionTo(mBroadcasting); + } else if (mTargetSHO.isGamingMode) { + transitionTo(mGamingMode); + } else if (mTargetSHO.isRecordingMode) { + transitionTo(mRecordingMode); + } else { + transitionTo(mActive); + } + } else if(ALREADY_ACTIVE == ret) { + transitionTo(mActive); + } + } + } + + class Deactivating extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mDeactivating; + } + if (mPrevState == mBroadcasting) { + mPrevState = mIdle; + } + Target.Device = null; + Target.Profile = Current.Profile; + mTargetSHO.copy(mSHOQueue); + mSHOQueue.reset(); + + ret = startSho(Target.Device, Target.Profile); + Log.d(TAG, "ret: " + ret); + if (SHO_SUCCESS == ret) { + transitionTo(mIdle); + } else if (SHO_PENDING == ret) { + sendMessageDelayed(Event.DEACTIVATE_TIMEOUT, DEACTIVATE_TIMEOUT_DELAY); + } else { + transitionTo(mPrevState); + } + } + + @Override + public void exit() { + removeMessages(Event.DEACTIVATE_TIMEOUT); + mPrevState = mDeactivating; + mPrevActiveDevice = Current.Device; + Current.Device = null; + mPrevActiveProfile = Current.Profile; + Current.Profile = ApmConst.AudioProfiles.NONE; // Add profile value here + } + + @Override + public boolean processMessage(Message message) { + log("Deactivating: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + log("New SHO request while handling previous. Add to queue"); + removeDeferredMessages(Event.SET_ACTIVE); + deferMessage(message); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + break; + + case Event.REMOVE_DEVICE: + break; + + case Event.DEVICE_REMOVED: + removeMessages(Event.DEACTIVATE_TIMEOUT); + transitionTo(mIdle); + break; + + case Event.DEACTIVATE_TIMEOUT: + transitionTo(mPrevState); + + case Event.STOP_SM: + deferMessage(message); + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Active extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mActive; + } + if(updatePending) { + if(mAudioType == AudioType.MEDIA) + sendActiveDeviceMediaUpdate(Current); + else if(mAudioType == AudioType.VOICE) + sendActiveDeviceVoiceUpdate(Current); + } + if(txStreamSuspended && mAudioType == AudioType.MEDIA) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } else if (mAudioType == AudioType.VOICE) { + lock.lock(); + voiceHandoffComplete.signal(); + lock.unlock(); + Log.d(TAG, "Voice Active: unlock by signal"); + } + } + + @Override + public void exit() { + //2 update dependent profiles + mPrevState = mActive; + mPrevActiveDevice = Current.Device; + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.saveVolume(mAudioType); + } + } + + @Override + public boolean processMessage(Message message) { + log("Active: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if(mSHOQueue.device == null) { + Log.w(TAG, "Invalid request"); + break; + } + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + // might have to handle + break; + + case Event.REMOVE_DEVICE: + transitionTo(mDeactivating); + break; + + case Event.DEVICE_REMOVED: + //might have to handle + break; + + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Gaming extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mGamingMode; + } + if(updatePending) { + sendActiveDeviceGamingUpdate(Current); + } + if(txStreamSuspended) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } + } + + @Override + public void exit() { + //2 update dependent profiles + mPrevState = mGamingMode; + mPrevActiveDevice = Current.Device; + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.saveVolume(mAudioType); + } + } + + @Override + public boolean processMessage(Message message) { + log("Gaming: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if(mSHOQueue.device == null) { + Log.w(TAG, "Invalid request"); + break; + } + if(!mSHOQueue.isUIReq && Current.Device.equals(mSHOQueue.device)) { + Log.w(TAG, "Spurious request for same device. Ignore"); + mSHOQueue.reset(); + break; + } + /*MediaAudio mMediaAudio = MediaAudio.get(); + if(mMediaAudio != null && mMediaAudio.isA2dpPlaying(mSHOQueue.device)) { + if(!(mSHOQueue.isBroadcast || mSHOQueue.isRecordingMode)) { + Log.w(TAG, "Gaming streaming is on"); + break; + } + }*/ + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + // might have to handle + break; + + case Event.REMOVE_DEVICE: + transitionTo(mDeactivating); + break; + + case Event.DEVICE_REMOVED: + //might have to handle + break; + + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Broadcasting extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mBroadcasting; + } + if (updatePending) { + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, mAudioType); + broadcastActiveDeviceChange(Current.Device, AudioType.MEDIA); + mAudioManager.avrcpSupportsAbsoluteVolume(Current.Device.getAddress(), false); + // Update active device to null in VolumeManager while enter broadcasting state + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(null, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + int rememberedVolume = 15; + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + Current.Device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, rememberedVolume); + updatePending = false; + } + if(txStreamSuspended) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } + } + + @Override + public void exit() { + mPrevState = mBroadcasting; + mPrevActiveDevice = Current.Device; + } + + @Override + public boolean processMessage(Message message) { + log("Broadcasting: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if(mSHOQueue.isUIReq) + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + break; + + case Event.REMOVE_DEVICE: + if(mSHOQueue.device == null) { + transitionTo(mDeactivating); + } else { + transitionTo(mActivating); + } + break; + + case Event.DEVICE_REMOVED: + break; + + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Recording extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mRecordingMode; + } + if(updatePending) { + sendActiveDeviceRecordingUpdate(Current); + } + mRecordingSuspended = false; + } + + @Override + public void exit() { + mPrevState = mRecordingMode; + mPrevActiveDevice = Current.Device; + HeadsetService hfpService = HeadsetService.getHeadsetService(); + + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + mPrevActiveDevice, + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.A2DP_SINK, + true, -1); + if(mRecordingSuspended) { + mAudioManager.setParameters("A2dpCaptureSuspend=false"); + mRecordingSuspended = false; + } + CallAudio mCallAudio = CallAudio.get(); + boolean isInCall = mCallAudio != null && + mCallAudio.isVoiceOrCallActive(); + if(isInCall) { + Log.d(TAG, " reset txStreamSuspended as call is active" ); + txStreamSuspended = false; + } + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.saveVolume(mAudioType); + } + } + + @Override + public boolean processMessage(Message message) { + log("Recording: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if (mSHOQueue.device == null) { + transitionTo(mDeactivating); + } else { + transitionTo(mActivating); + } + break; + + case Event.ACTIVE_DEVICE_CHANGE: + break; + + case Event.REMOVE_DEVICE: + transitionTo(mDeactivating); + break; + + case Event.DEVICE_REMOVED: + //might have to handle + break; + case Event.SUSPEND_RECORDING: { + if(mRecordingSuspended) break; + mAudioManager.setParameters("A2dpCaptureSuspend=true"); + mRecordingSuspended = true; + } break; + + case Event.RESUME_RECORDING: { + if(!mRecordingSuspended) break; + mAudioManager.setParameters("A2dpCaptureSuspend=false"); + mRecordingSuspended = false; + } break; + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + void sendActiveDeviceMediaUpdate(DeviceProfileCombo Current) { + if(Current.Profile == ApmConst.AudioProfiles.HAP_BREDR) { + if(mPrevActiveDevice != null) { + broadcastActiveDeviceChange (null, AudioType.MEDIA ); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(null, ApmConst.AudioFeatures.MEDIA_AUDIO); + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + mPrevActiveDevice, BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.A2DP, true, -1); + } + return; + } + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, AudioType.MEDIA); + Log.d(TAG, "sendActiveDeviceMediaUpdate: mPrevActiveDevice: " + + mPrevActiveDevice + ", Current.Device: " + Current.Device); + + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.refreshCurrentCodec(Current.Device); + + broadcastActiveDeviceChange (Current.absoluteDevice, AudioType.MEDIA); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.MEDIA_AUDIO); + if(Current.Profile == ApmConst.AudioProfiles.A2DP) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + mA2dpService.broadcastActiveCodecConfig(); + } + //2 Update dependent profiles + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + + McpService mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.SetActiveDevices(Current.absoluteDevice, Current.Profile); + } + int deviceVolume = 7; + if(mVolumeManager != null) { + deviceVolume = mVolumeManager.getActiveVolume(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + if(mAudioManager != null) { + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + Current.Device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, deviceVolume); + } + updatePending = false; + } + + void sendActiveDeviceGamingUpdate(DeviceProfileCombo Current) { + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, AudioType.MEDIA); + + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.refreshCurrentCodec(Current.Device); + + broadcastActiveDeviceChange (Current.absoluteDevice, AudioType.MEDIA); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.MEDIA_AUDIO); + + //2 Update dependent profiles + VolumeManager mVolumeManager = VolumeManager.get(); + int deviceVolume = 7; + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, + ApmConst.AudioFeatures.MEDIA_AUDIO); + deviceVolume = mVolumeManager.getActiveVolume(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + if(mAudioManager != null) { + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + Current.Device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, deviceVolume); + + /*Add back channel call here*/ + } + updatePending = false; + } + + void sendActiveDeviceVoiceUpdate(DeviceProfileCombo Current) { + Log.d(TAG, "sendActiveDeviceVoiceUpdate"); + if(Current.Profile == ApmConst.AudioProfiles.HAP_BREDR) { + if(mPrevActiveDevice != null) { + broadcastActiveDeviceChange (null, AudioType.VOICE); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.CALL_AUDIO); + } + return; + } + broadcastActiveDeviceChange (Current.absoluteDevice, AudioType.VOICE); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.CALL_AUDIO); + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, + ApmConst.AudioFeatures.CALL_AUDIO); + } + CCService ccService = CCService.getCCService(); + if (ccService != null) { + ccService.setActiveDevice(Current.absoluteDevice); + } + + if(mTargetSHO.PlayReq) { + CallAudio mCallAudio = CallAudio.get(); + mCallAudio.connectAudio(); + } + + updatePending = false; + } + + void sendActiveDeviceRecordingUpdate(DeviceProfileCombo Current) { + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, AudioType.MEDIA); + + Log.d(TAG, "sendActiveDeviceRecordingUpdate: mPrevActiveDevice: " + + mPrevActiveDevice + ", Current.Device: " + Current.Device); + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.refreshCurrentCodec(Current.Device); + if (mAudioManager != null) { + mAudioManager.handleBluetoothA2dpActiveDeviceChange(Current.Device, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, false, -1); // TO-Check + } + updatePending = false; + } + + boolean isSameProfile (int p1, int p2, int audioType) { + if(p1 == p2) { + return true; + } + + if(audioType == AudioType.MEDIA) { + int leMediaMask = ApmConst.AudioProfiles.TMAP_MEDIA | + ApmConst.AudioProfiles.BAP_MEDIA | + ApmConst.AudioProfiles.BAP_RECORDING | + ApmConst.AudioProfiles.BAP_GCP; + if((leMediaMask & p1) > 0 && (leMediaMask & p2) > 0) { + return true; + } + } else if(audioType == AudioType.VOICE) { + int leVoiceMask = ApmConst.AudioProfiles.TMAP_CALL | ApmConst.AudioProfiles.BAP_CALL; + if((leVoiceMask & p1) > 0 && (leVoiceMask & p2) > 0) { + return true; + } + } + + return false; + } + } + + static class AudioType { + public static int VOICE = ApmConst.AudioFeatures.CALL_AUDIO; + public static int MEDIA = ApmConst.AudioFeatures.MEDIA_AUDIO; + + public static int SIZE = 2; + } + + private class SHOReq { + BluetoothDevice device; + boolean PlayReq; + int retryCount; + boolean isBroadcast; + boolean isGamingMode; + boolean isRecordingMode; + boolean isUIReq; + boolean forceStopAudio; + + void copy(SHOReq src) { + device = src.device; + PlayReq = src.PlayReq; + retryCount = src.retryCount; + isBroadcast = src.isBroadcast; + isGamingMode = src.isGamingMode; + isRecordingMode = src.isRecordingMode; + isUIReq = src.isUIReq; + forceStopAudio = src.forceStopAudio; + } + + void reset() { + device = null; + PlayReq = false; + retryCount = 0; + isBroadcast = false; + isGamingMode = false; + isRecordingMode = false; + isUIReq = false; + forceStopAudio = false; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java new file mode 100644 index 0000000000000000000000000000000000000000..45fab0dfabc7649d731ba9f1d68e238fd44d0e99 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java @@ -0,0 +1,70 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +public class ApmConst { + + private static boolean leAudioEnabled = false; + public static final String groupAddress = "9E:8B:00:00:00"; + + public static boolean getLeAudioEnabled() { + return leAudioEnabled; + } + + protected static void setLeAudioEnabled(boolean leAudioSupport) { + leAudioEnabled = leAudioSupport; + } + + public static class AudioFeatures { + + public static final int CALL_AUDIO = 0; + public static final int MEDIA_AUDIO = 1; + public static final int CALL_CONTROL = 2; + public static final int MEDIA_CONTROL = 3; + public static final int MEDIA_VOLUME_CONTROL = 4; + public static final int CALL_VOLUME_CONTROL = 5; + public static final int BROADCAST_AUDIO = 6; + public static final int HEARING_AID = 7; + public static final int MAX_AUDIO_FEATURES = 8; + + } + + public static class AudioProfiles { + + public static final int NONE = 0x0000; + public static final int A2DP = 0x0001; + public static final int HFP = 0x0002; + public static final int AVRCP = 0x0004; + public static final int TMAP_MEDIA = 0x0008; + public static final int BAP_MEDIA = 0x0010; + public static final int MCP = 0x0020; + public static final int CCP = 0x0040; + public static final int VCP = 0x0080; + public static final int HAP_BREDR = 0x0100; + public static final int HAP_LE = 0x0200; + public static final int BROADCAST_BREDR = 0x0400; + public static final int BROADCAST_LE = 0x0800; + public static final int TMAP_CALL = 0x1000; + public static final int BAP_CALL = 0x2000; + public static final int BAP_GCP = 0x4000; + public static final int BAP_RECORDING = 0x8000; + + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..b4370237c28d419eeda49e06b46c4b3cfb289a89 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java @@ -0,0 +1,131 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; +import java.util.List; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * A2DP Native Interface to/from JNI. + */ +public class ApmNativeInterface { + private static final String TAG = "ApmNativeInterface"; + private static final boolean DBG = true; + + @GuardedBy("INSTANCE_LOCK") + private static ApmNativeInterface sInstance; + private BluetoothAdapter mAdapter; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + @VisibleForTesting + private ApmNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.w(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static ApmNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new ApmNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + */ + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + public void cleanup() { + cleanupNative(); + } + + /** + * Report new active device to stack. + * + * @param device: new active device + * @param profile: new active profile + * @return true on success, otherwise false. + */ + public boolean activeDeviceUpdate(BluetoothDevice device, int profile, int audioType) { + return activeDeviceUpdateNative(getByteAddress(device), profile, audioType); + } + + /** + * Report Content Control ID to stack. + * + * @param id: Content Control ID + * @param profile: content control profile + * @return true on success, otherwise false. + */ + public boolean setContentControl(int id, int profile) { + return setContentControlNative(id, profile); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + //Current logic is implemented only for CALL_AUDIO + //Proper Audio_type needs to be sent to device profile map + //for other audio features + private int getActiveProfile(byte[] address, int audio_type) { + DeviceProfileMap dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + BluetoothDevice device = getDevice(address); + int profile = dpm.getProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + return profile; + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean activeDeviceUpdateNative(byte[] address, int profile, int audioType); + private native boolean setContentControlNative(int id, int profile); +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java new file mode 100644 index 0000000000000000000000000000000000000000..0d846912500d10a1f8998aee2b5824b01a0666dc --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java @@ -0,0 +1,758 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import com.android.bluetooth.hfp.HeadsetService; +import com.android.bluetooth.hfp.HeadsetA2dpSync; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.apm.MediaAudio; +import com.android.bluetooth.apm.CallControl; +import android.media.AudioManager; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.AdapterService; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.content.Context; +import java.lang.Integer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import android.util.Log; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import android.content.Intent; +import android.os.UserHandle; + +public class CallAudio { + + private static CallAudio mCallAudio; + private static final String TAG = "APM: CallAudio"; + Map mCallDevicesMap; + private Context mContext; + private AudioManager mAudioManager; + private ActiveDeviceManagerService mActiveDeviceManager; + private AdapterService mAdapterService; + public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + public static final String BLUETOOTH_PRIVILEGED = + android.Manifest.permission.BLUETOOTH_PRIVILEGED; + private static final int MAX_DEVICES = 200; + public boolean mVirtualCallStarted; + private CallControl mCallControl = null; + + private CallAudio(Context context) { + Log.d(TAG, "Initialization"); + mContext = context; + mCallDevicesMap = new ConcurrentHashMap(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mActiveDeviceManager = ActiveDeviceManagerService.get(); + mAdapterService = AdapterService.getAdapterService(); + mCallControl = CallControl.get(); + } + + public static CallAudio init(Context context) { + if(mCallAudio == null) { + mCallAudio = new CallAudio(context); + CallAudioIntf.init(mCallAudio); + } + return mCallAudio; + } + + public static CallAudio get() { + return mCallAudio; + } + + public boolean connect(BluetoothDevice device) { + Log.i(TAG, "connect: " + device); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(device == null) + return false; + boolean status; + if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); + return false; + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (dMap == null) + return false; + + int profileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if (profileID == ApmConst.AudioProfiles.NONE) { + Log.e(TAG, "Can Not connect to " + device + ". Device does not support call service."); + return false; + } + + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + if(mCallDevicesMap.size() >= MAX_DEVICES) + return false; + mCallDevice = new CallDevice(device, profileID); + mCallDevicesMap.put(device.getAddress(), mCallDevice); + } else if(mCallDevice.deviceConnStatus != BluetoothProfile.STATE_DISCONNECTED) { + Log.i(TAG, "Device already connected"); + return false; + } + + if((ApmConst.AudioProfiles.HFP & profileID) == ApmConst.AudioProfiles.HFP) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + return false; + } + service.connectHfp(device); + } + + StreamAudioService mStreamService = StreamAudioService.getStreamAudioService(); + if(mStreamService != null && + (ApmConst.AudioProfiles.BAP_CALL & profileID) == ApmConst.AudioProfiles.BAP_CALL) { + mStreamService.connectLeStream(device, profileID); + } + return true; + } + + public boolean connect(BluetoothDevice device, Boolean allProfiles) { + if(allProfiles) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (dMap == null) + return false; + + int profileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if((ApmConst.AudioProfiles.HFP & profileID) == ApmConst.AudioProfiles.HFP) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + return false; + } + return service.connectHfp(device); + } else { + /*Common connect for LE Media and Call handled from StreamAudioService*/ + return true; + } + } + + return connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + Log.i(TAG, " disconnect: " + device); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); + CallDevice mCallDevice; + + if(device == null) + return false; + + mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + Log.e(TAG, "Ignore: Device " + device + " not present in list"); + return false; + } + + if (mCallDevice.profileConnStatus[CallDevice.SCO_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.disconnectHfp(device); + } + } + + if (mCallDevice.profileConnStatus[CallDevice.LE_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + service.disconnectLeStream(device, true, false); + } + } + + return true; + } + + public boolean disconnect(BluetoothDevice device, Boolean allProfiles) { + if(allProfiles) { + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + Log.e(TAG, "Ignore: Device " + device + " not present in list"); + return false; + } + if(mCallDevice.profileConnStatus[CallDevice.SCO_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + return disconnect(device); + } else { + /*Common connect for LE Media and Call handled from StreamAudioService*/ + return true; + } + } + + return disconnect(device); + } + + public boolean startScoUsingVirtualVoiceCall() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + Log.d(TAG, "startScoUsingVirtualVoiceCall"); + BluetoothDevice mActivedevice = null; + int profile; + mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + if(mActivedevice == null) { + Log.e(TAG, "startScoUsingVirtualVoiceCall failed. Active Device is null"); + return false; + } + } else { + return false; + } + + checkA2dpState(); + + profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + switch(profile) { + case ApmConst.AudioProfiles.HFP: + HeadsetService headsetService = HeadsetService.getHeadsetService(); + if(headsetService != null) { + if(headsetService.startScoUsingVirtualVoiceCall()) { + mVirtualCallStarted = true; + return true; + } + } + break; + case ApmConst.AudioProfiles.BAP_CALL: + case ApmConst.AudioProfiles.TMAP_CALL: + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + if(mStreamAudioService != null) { + if(mStreamAudioService.startStream(mActivedevice)) { + mVirtualCallStarted = true; + mCallControl = CallControl.get(); + if (mCallControl != null) { + mCallControl.setVirtualCallActive(true); + } + return true; + } + } + break; + default: + Log.e(TAG, "Unhandled profile"); + break; + } + + Log.e(TAG, "startScoUsingVirtualVoiceCall failed. Device: " + mActivedevice); + if(ApmConst.AudioProfiles.HFP != profile) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + return false; + } + + public boolean stopScoUsingVirtualVoiceCall() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + Log.d(TAG, "stopScoUsingVirtualVoiceCall"); + BluetoothDevice mActivedevice = null; + int profile; + mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + if(mActivedevice == null) { + Log.e(TAG, "stopScoUsingVirtualVoiceCall failed. Active Device is null"); + return false; + } + } else { + return false; + } + + profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + switch(profile) { + case ApmConst.AudioProfiles.HFP: + HeadsetService headsetService = HeadsetService.getHeadsetService(); + if(headsetService != null) { + mVirtualCallStarted = false; + return headsetService.stopScoUsingVirtualVoiceCall(); + } + break; + case ApmConst.AudioProfiles.BAP_CALL: + case ApmConst.AudioProfiles.TMAP_CALL: + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + if(mStreamAudioService != null) { + mVirtualCallStarted = false; + mCallControl = CallControl.get(); + if (mCallControl != null) { + mCallControl.setVirtualCallActive(false); + } + return mStreamAudioService.stopStream(mActivedevice); + } + break; + default: + Log.e(TAG, "Unhandled profile"); + break; + } + + Log.e(TAG, "stopScoUsingVirtualVoiceCall failed. Device: " + mActivedevice); + return false; + } + + void remoteDisconnectVirtualVoiceCall(BluetoothDevice device) { + if(device == null) + return; + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(device.equals(mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO)) && + mActiveDeviceManager.isStableState(ApmConst.AudioFeatures.CALL_AUDIO)) { + stopScoUsingVirtualVoiceCall(); + } + } + + int getProfile(BluetoothDevice mDevice) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + int profileID = dMap.getProfile(mDevice, ApmConst.AudioFeatures.CALL_AUDIO); + Log.d(TAG," getProfile for device " + mDevice + " profileID " + profileID); + return profileID; + } + + void checkA2dpState() { + MediaAudio sMediaAudio = MediaAudio.get(); + BluetoothDevice sMediaActivedevice = + mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + //if(sMediaAudio.isA2dpPlaying(sMediaActivedevice)) { + Log.d(TAG," suspendA2DP isA2dpPlaying true " + " for device " + sMediaActivedevice); + int profileID = mActiveDeviceManager.getActiveProfile( + ApmConst.AudioFeatures.CALL_AUDIO); + if(ApmConst.AudioProfiles.HFP != profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().suspendA2DP( + HeadsetA2dpSync.A2DP_SUSPENDED_BY_CS_CALL, null); + } + } + //} + } + + public boolean connectAudio() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + BluetoothDevice mActivedevice = null; + boolean status = false; + + mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + } + Log.i(TAG, "connectAudio: device=" + mActivedevice + ", " + Utils.getUidPidString()); + + int profileID = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + checkA2dpState(); + + if(ApmConst.AudioProfiles.HFP == profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + status = false; + } + status = service.connectAudio(mActivedevice); + } else if(ApmConst.AudioProfiles.BAP_CALL == profileID) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + status = service.startStream(mActivedevice); + } + } else { + Log.e(TAG, "Unhandled connect audio request for profile: " + profileID); + status = false; + } + + if(status == false) { + Log.e(TAG, "failed connect audio request for device: " + mActivedevice); + if(ApmConst.AudioProfiles.HFP != profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + } + + return status; + } + + public boolean disconnectAudio() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + BluetoothDevice mActivedevice = null; + boolean mStatus = false; + + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + } + Log.i(TAG, "disconnectAudio: device=" + mActivedevice + ", " + Utils.getUidPidString()); + + int profileID = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + + if(ApmConst.AudioProfiles.HFP == profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + mStatus = false; + } + mStatus = service.disconnectAudio(); + } else if(ApmConst.AudioProfiles.BAP_CALL == profileID) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + mStatus = service.stopStream(mActivedevice); + } + } else { + Log.e(TAG, "Unhandled disconnectAudio request for profile: " + profileID); + mStatus = true; + } + + if(ApmConst.AudioProfiles.HFP != profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + return mStatus; + } + + public boolean setConnectionPolicy(BluetoothDevice device, Integer connectionPolicy) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, + "Need BLUETOOTH_PRIVILEGED permission"); + boolean mStatus; + + Log.d(TAG, "setConnectionPolicy: device=" + device + + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString()); + + mStatus = mAdapterService.getDatabase() + .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, connectionPolicy); + + if (mStatus && + connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + connect(device); + } else if (mStatus && + connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + disconnect(device); + } + return mStatus; + } + + public int getConnectionPolicy(BluetoothDevice device) { + if(mAdapterService != null) { + int connPolicy; + connPolicy = mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET); + Log.d(TAG, "getConnectionPolicy: device=" + device + + ", connectionPolicy=" + connPolicy); + return connPolicy; + } else { + return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + + } + } + + public int getAudioState(BluetoothDevice device) { + if(device == null) + return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; + CallDevice mCallDevice; + mCallDevice = mCallDevicesMap.get(device.getAddress()); + if (mCallDevice == null) { + Log.w(TAG, "getAudioState: device " + device + " was never connected/connecting"); + return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; + } + return mCallDevice.scoStatus; + } + + private List getNonIdleAudioDevices() { + if(mCallDevicesMap.size() == 0) { + return new ArrayList<>(0); + } + + ArrayList devices = new ArrayList<>(); + for (CallDevice mCallDevice : mCallDevicesMap.values()) { + if (mCallDevice.scoStatus != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + devices.add(mCallDevice.mDevice); + } + } + return devices; + } + + public boolean isAudioOn() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + int numConnectedAudioDevices = getNonIdleAudioDevices().size(); + Log.d(TAG," isAudioOn: The number of audio connected devices " + + numConnectedAudioDevices); + return numConnectedAudioDevices > 0; + } + + public List getConnectedDevices() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Log.i(TAG, "getConnectedDevices: "); + if(mCallDevicesMap.size() == 0) { + Log.i(TAG, "no device is Connected:"); + return new ArrayList<>(0); + } + + List connectedDevices = new ArrayList<>(); + for(CallDevice mCallDevice : mCallDevicesMap.values()) { + if(mCallDevice.deviceConnStatus == BluetoothProfile.STATE_CONNECTED) { + connectedDevices.add(mCallDevice.mDevice); + } + } + Log.i(TAG, "ConnectedDevices: = " + connectedDevices.size()); + return connectedDevices; + } + + public int getConnectionState(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + + if(device == null) + return BluetoothProfile.STATE_DISCONNECTED; + CallDevice mCallDevice; + mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice != null) + return mCallDevice.deviceConnStatus; + + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isVoiceOrCallActive() { + boolean isVoiceActive = isAudioOn() || mVirtualCallStarted; + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService != null) { + isVoiceActive = isVoiceActive || mHeadsetService.isScoOrCallActive(); + } + return isVoiceActive; + } + + private void broadcastConnStateChange(BluetoothDevice device, int fromState, int toState) { + Log.d(TAG,"broadcastConnectionState " + device + ": " + fromState + "->" + toState); + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService == null) { + Log.w(TAG,"broadcastConnectionState: HeadsetService not initialized. Return!"); + return; + } + + Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private void broadcastAudioState(BluetoothDevice device, int fromState, int toState) { + Log.d(TAG,"broadcastAudioState " + device + ": " + fromState + "->" + toState); + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService == null) { + Log.d(TAG,"broadcastAudioState: HeadsetService not initialized. Return!"); + return; + } + + Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + public void onConnStateChange(BluetoothDevice device, Integer state, Integer profile) { + int prevState; + Log.w(TAG, "onConnStateChange: profile: " + profile + " state: " + + state + " for device " + device); + if(device == null) + return; + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + + if(mCallDevice == null) { + if(state == BluetoothProfile.STATE_DISCONNECTED) + return; + if(mCallDevicesMap.size() >= MAX_DEVICES) { + return; + } + mCallDevice = new CallDevice(device, profile, state); + mCallDevicesMap.put(device.getAddress(), mCallDevice); + broadcastConnStateChange(device, BluetoothProfile.STATE_DISCONNECTED, state); + return; + } + + int profileIndex = mCallDevice.getProfileIndex(profile); + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + prevState = mCallDevice.deviceConnStatus; + mCallDevice.profileConnStatus[profileIndex] = state; + + if(state == BluetoothProfile.STATE_DISCONNECTED) { + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.CALL_AUDIO, profile, false); + } + + int otherProfileConnectionState = mCallDevice.profileConnStatus[(profileIndex+1)%2]; + Log.w(TAG, " otherProfileConnectionState: " + otherProfileConnectionState); + + switch(otherProfileConnectionState) { + /*Send Broadcast based on state of other profile*/ + case BluetoothProfile.STATE_DISCONNECTED: + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + if(state == BluetoothProfile.STATE_CONNECTED) { + int supportedProfiles = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if(profile == ApmConst.AudioProfiles.HFP && + (supportedProfiles & ApmConst.AudioProfiles.BAP_CALL) == ApmConst.AudioProfiles.BAP_CALL) { + Log.w(TAG, "Connect LE Voice after HFP auto connect from remote"); + StreamAudioService mStreamService = StreamAudioService.getStreamAudioService(); + if(mStreamService != null) { + mStreamService.connectLeStream(device, ApmConst.AudioProfiles.BAP_CALL); + } + } else { + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + break; + case BluetoothProfile.STATE_CONNECTING: + int preferredProfile = dMap.getProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + boolean isPreferredProfile = (preferredProfile == profile); + if(state == BluetoothProfile.STATE_CONNECTED && isPreferredProfile) { + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_DISCONNECTING: + if(state == BluetoothProfile.STATE_CONNECTING || + state == BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_CONNECTED: + if(state == BluetoothProfile.STATE_CONNECTED) { + if(prevState != state) { + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + } + ActiveDeviceManagerService mActiveDeviceManager = + ActiveDeviceManagerService.get(); + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.CALL_AUDIO); + } else if(state == BluetoothProfile.STATE_DISCONNECTED) { + if(prevState != BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, BluetoothProfile.STATE_CONNECTED); + mCallDevice.deviceConnStatus = BluetoothProfile.STATE_CONNECTED; + } else { + ActiveDeviceManagerService mActiveDeviceManager = + ActiveDeviceManagerService.get(); + if(device.equals(mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO))) { + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + } + break; + } + + if(state == BluetoothProfile.STATE_CONNECTED) { + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.CALL_AUDIO, profile, true); + } + } + + public void onAudioStateChange(BluetoothDevice device, Integer state) { + int prevStatus; + if(device == null) + return; + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + return; + } + + if(mCallDevice.scoStatus == state) + return; + + HeadsetService service = HeadsetService.getHeadsetService(); + int profileID = mActiveDeviceManager.getActiveProfile( + ApmConst.AudioFeatures.CALL_AUDIO); + BluetoothDevice mActivedevice = mActiveDeviceManager.getActiveDevice( + ApmConst.AudioFeatures.CALL_AUDIO); + if (service != null) { + if(!(service.shouldCallAudioBeActive() || mVirtualCallStarted)) { + if(ApmConst.AudioProfiles.BAP_CALL == profileID) { + StreamAudioService mStreamAudioService = + StreamAudioService.getStreamAudioService(); + if(mStreamAudioService != null) { + Log.w(TAG, "Call not active, disconnect stream"); + mStreamAudioService.stopStream(mActivedevice); + } + } + } + } + + prevStatus = mCallDevice.scoStatus; + mCallDevice.scoStatus = state; + VolumeManager mVolumeManager = VolumeManager.get(); + mVolumeManager.updateStreamState(device, state, ApmConst.AudioFeatures.CALL_AUDIO); + broadcastAudioState(device, prevStatus, state); + if(state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + if(ApmConst.AudioProfiles.HFP != profileID) { + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + //mAudioManager.setBluetoothScoOn(false); + } /*else { + mAudioManager.setBluetoothScoOn(true); + }*/ + } + + public void setAudioParam(String param) { + mAudioManager.setParameters(param); + } + + public void setBluetoothScoOn(boolean on) { + mAudioManager.setBluetoothScoOn(on); + } + + public AudioManager getAudioManager() { + return mAudioManager; + } + + class CallDevice { + BluetoothDevice mDevice; + int[] profileConnStatus = new int[2]; + int deviceConnStatus; + int scoStatus; + + public static final int SCO_STREAM = 0; + public static final int LE_STREAM = 1; + + CallDevice(BluetoothDevice device, int profile, int state) { + mDevice = device; + if(profile == ApmConst.AudioProfiles.HFP) { + profileConnStatus[SCO_STREAM] = state; + profileConnStatus[LE_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + } else { + profileConnStatus[LE_STREAM] = state; + profileConnStatus[SCO_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + } + deviceConnStatus = state; + scoStatus = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;; + } + + CallDevice(BluetoothDevice device, int profile) { + this(device, profile, BluetoothProfile.STATE_DISCONNECTED); + } + + public int getProfileIndex(int profile) { + if(profile == ApmConst.AudioProfiles.HFP) + return SCO_STREAM; + else + return LE_STREAM; + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java new file mode 100644 index 0000000000000000000000000000000000000000..40e5fd15cd00204d96315f6be69a8b0723bbec3d --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java @@ -0,0 +1,164 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; +import com.android.bluetooth.hfp.HeadsetService; +import com.android.bluetooth.hfp.HeadsetSystemInterface; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.cc.CCService; +import android.media.AudioManager; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; +import android.net.Uri; +import android.telephony.PhoneNumberUtils; +import android.telecom.PhoneAccount; +import android.os.SystemProperties; + +import android.util.Log; + + +public class CallControl { + + private static CallControl mCallControl; + private static final String TAG = "CallControl"; + private static Context mContext; + private static ActiveDeviceManagerService mActiveDeviceManager; + private static boolean isCCEnabled = true; + private CallControl(Context context) { + Log.d(TAG, "Initialization"); + mContext = context; + } + + public static void init(Context context) { + if(mCallControl == null) { + mCallControl = new CallControl(context); + CallControlIntf.init(mCallControl); + } + isCCEnabled = SystemProperties.getBoolean("persist.vendor.service.bt.cc", true); + Log.d(TAG, "isCCEnabled" + isCCEnabled); + } + + public static CallControl get() { + return mCallControl; + } + + public void phoneStateChanged(Integer numActive, Integer numHeld, Integer callState, String number, + Integer type, String name, Boolean isVirtualCall) { + Log.d(TAG, "phoneStateChanged"); + if(isCCEnabled == true) { + CCService.getCCService().phoneStateChanged(numActive, numHeld,callState,number, + type, name, isVirtualCall); + } + } + + public void setVirtualCallActive(boolean state) { + if(isCCEnabled == true) { + CCService.getCCService().setVirtualCallActive(state); + } + } + + public void clccResponse(Integer index, Integer direction, Integer status, Integer mode, Boolean mpty, + String number, Integer type) { + Log.d(TAG, "clccResponse"); + if (isCCEnabled == true) { + CCService.getCCService().clccResponse(index, direction, status, mode, mpty, number, type); + } + } + + public void updateBearerTechnology(Integer tech) { + Log.d(TAG, "updateBearerTechnology"); + if (isCCEnabled == true) { + CCService.getCCService().updateBearerProviderTechnology(tech); + } + } + + public void updateSignalStatus(Integer signal) { + Log.d(TAG, "updateSignalStatus"); + if (isCCEnabled == true) { + CCService.getCCService().updateSignalStrength(signal); + } + } + + public void updateBearerName(String name) { + Log.d(TAG, "updateBearerProviderName"); + if (isCCEnabled == true) { + CCService.getCCService().updateBearerProviderName(name); + } + } + + public void updateOriginateResult(BluetoothDevice device, Integer event, Integer res) { + Log.d(TAG, "updateOriginateResult"); + if (isCCEnabled == true) { + CCService.getCCService().updateOriginateResult(device, event, res); + } + } + public static void listenForPhoneState (int events) { + Log.d(TAG, "listenForPhoneState"); + BluetoothDevice dummyDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("CC:CC:CC:CC:CC:CC"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().getHeadsetPhoneState().listenForPhoneState(dummyDevice, events); + } + + public static void answerCall (BluetoothDevice device) { + Log.d(TAG, "answerCall"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().answerCall(device); + } + + public static void hangupCall (BluetoothDevice device) { + Log.d(TAG, "hangupCall"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().hangupCall(device); + } + + public static void terminateCall (BluetoothDevice device, int index) { + Log.d(TAG, "terminateCall"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().terminateCall(device, index); + } + + public static boolean processChld (BluetoothDevice device, int chld) { + Log.d(TAG, "processChld"); + return HeadsetService.getHeadsetService().getSystemInterfaceObj().processChld(chld); + } + + public static boolean holdCall (BluetoothDevice device, int index) { + Log.d(TAG, "holdCall"); + return HeadsetService.getHeadsetService().getSystemInterfaceObj().holdCall(index); + } + + public static boolean listCurrentCalls () { + Log.d(TAG, "listCurrentCalls"); + return HeadsetService.getHeadsetService().getSystemInterfaceObj().listCurrentCalls(); + } + + public static boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) { + Log.i(TAG, "dialOutgoingCall: from " + fromDevice); + HeadsetService service = HeadsetService.getHeadsetService(); + if (service != null) { + service.dialOutgoingCallInternal(fromDevice, dialNumber); + } + return true; + } + + public static void dial (BluetoothDevice device, String dialNumber) { + dialOutgoingCall(device, dialNumber); + } + +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java new file mode 100644 index 0000000000000000000000000000000000000000..0901f98618174fa449682348ec2906c97b7ce646 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java @@ -0,0 +1,814 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import android.content.SharedPreferences; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.os.SystemProperties; +import android.util.Log; +import android.content.Context; +import com.android.internal.util.ArrayUtils; +import java.util.HashMap; +import java.util.Map; +import java.util.Arrays; +import java.util.ArrayList; +import java.lang.Integer; +import java.lang.Boolean; +import java.util.Objects; + +public class DeviceProfileMap { + + Map mSupportedProfileMap = new HashMap(); + Map mActiveProfileMap = new HashMap(); + Map mConnectedProfileMap = new HashMap(); + private Context mContext; + private static DeviceProfileMap DPMSingleInstance = null; + public static int [] mPreferredProfileList = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + public static final String SUPPORTED_PROFILE_MAP = "bluetooth_supported_profile_map"; + public final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + public final String ACTION_POWER_OFF = "android.intent.action.QUICKBOOT_POWEROFF"; + private final Object mLock = new Object(); + + private static final int LeMediaProfiles = ApmConst.AudioProfiles.BAP_MEDIA + | ApmConst.AudioProfiles.TMAP_MEDIA + | ApmConst.AudioProfiles.BAP_GCP + | ApmConst.AudioProfiles.BAP_RECORDING; + + private static final int LeCallProfiles = ApmConst.AudioProfiles.BAP_CALL + | ApmConst.AudioProfiles.TMAP_CALL; + + // private constructor restricted to this class itself + private DeviceProfileMap() { + Log.w(LOGTAG, "DeviceProfileMap object creation"); + } + + // static method to create instance of Singleton class + public static DeviceProfileMap getDeviceProfileMapInstance() { + if (DPMSingleInstance == null) { + DPMSingleInstance = new DeviceProfileMap(); + DeviceProfileMapIntf.init(DPMSingleInstance); + } + return DPMSingleInstance; + } + + private static final String LOGTAG = "DeviceProfileMap"; + private SharedPreferences getSupportedProfileMap() { + return mContext.getSharedPreferences(SUPPORTED_PROFILE_MAP, Context.MODE_PRIVATE); + } + + /** + * Initialize the device profile map + */ + public synchronized boolean init(Context context) { + Log.d(LOGTAG, "init: "); + // populate the supported profile list. + mContext = context; + Map allKeys = getSupportedProfileMap().getAll(); + SharedPreferences.Editor mSupportedProfileMapEditor = getSupportedProfileMap().edit(); + + for (Map.Entry entry : allKeys.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + BluetoothDevice mBluetoothDevice = BluetoothAdapter.getDefaultAdapter(). + getRemoteDevice(key); + if (value instanceof Integer && mBluetoothDevice.getBondState() + == BluetoothDevice.BOND_BONDED) { + mSupportedProfileMap.put(mBluetoothDevice, (Integer) value); + Log.d(LOGTAG, "address " + key + " from the Supported Profile Map: " + value); + } else { + Log.d(LOGTAG, "Removing " + key + " from the Supported Profile map"); + mSupportedProfileMapEditor.remove(key); + } + } + mSupportedProfileMapEditor.apply(); + //intialize the preferred profile list + int mPreferredProfileVal = + SystemProperties.getInt("persist.vendor.qcom.bluetooth.default_profiles", 0); + Log.d(LOGTAG, "init: Preferred Profile = " + mPreferredProfileVal); + int mfeature = 0; + while (mfeature < ApmConst.AudioFeatures.MAX_AUDIO_FEATURES) { + switch(mfeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + /* default preferred profile for call */ + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.HFP; + if((mPreferredProfileVal & + ApmConst.AudioProfiles.TMAP_CALL) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.TMAP_CALL; + } else if((mPreferredProfileVal & + ApmConst.AudioProfiles.BAP_CALL) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.BAP_CALL; + } + break; + case ApmConst.AudioFeatures.MEDIA_AUDIO: + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.A2DP; + if((mPreferredProfileVal & + ApmConst.AudioProfiles.TMAP_MEDIA) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.TMAP_MEDIA; + } else if((mPreferredProfileVal & + ApmConst.AudioProfiles.BAP_RECORDING) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.BAP_RECORDING; + } else if((mPreferredProfileVal & + ApmConst.AudioProfiles.BAP_MEDIA) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.BAP_MEDIA; + } + break; + case ApmConst.AudioFeatures.CALL_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.CCP) != 0) ? + ApmConst.AudioProfiles.CCP : ApmConst.AudioProfiles.HFP; + break; + case ApmConst.AudioFeatures.MEDIA_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.AVRCP) != 0) ? + ApmConst.AudioProfiles.AVRCP : ApmConst.AudioProfiles.MCP; + break; + case ApmConst.AudioFeatures.CALL_VOLUME_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + (ApmConst.AudioProfiles.BAP_CALL | ApmConst.AudioProfiles.TMAP_CALL)) != 0) ? + ApmConst.AudioProfiles.VCP : ApmConst.AudioProfiles.HFP; + break; + case ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.AVRCP) != 0) ? + ApmConst.AudioProfiles.AVRCP : ApmConst.AudioProfiles.VCP; + break; + case ApmConst.AudioFeatures.HEARING_AID: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.HAP_BREDR) != 0) ? + ApmConst.AudioProfiles.HAP_BREDR : ApmConst.AudioProfiles.HAP_LE; + break; + case ApmConst.AudioFeatures.BROADCAST_AUDIO: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.BROADCAST_BREDR) != 0) ? + ApmConst.AudioProfiles.BROADCAST_BREDR : ApmConst.AudioProfiles.BROADCAST_LE; + break; + default : + break; + } + Log.w(LOGTAG, "init: Preferred Profile = " + mPreferredProfileList[mfeature] + + " for audio feature " + mfeature); + mfeature++; + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_UUID); + filter.addAction(ACTION_SHUTDOWN); + filter.addAction(ACTION_POWER_OFF); + mContext.registerReceiver(mDeviceProfileMapReceiver, filter); + + return true; + } + + private final BroadcastReceiver mDeviceProfileMapReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.w(LOGTAG, "mDeviceProfileMapReceiver, action is null"); + return; + } + switch (action) { + case BluetoothDevice.ACTION_UUID: { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); + handleDeviceUuidEvent(device, uuids); + break; + } + case ACTION_SHUTDOWN: + case ACTION_POWER_OFF: + handleDeviceShutdown(); + break; + default: + Log.w(LOGTAG, "Unknown action " + action); + } + } + }; + + private void handleDeviceUuidEvent(BluetoothDevice device, Parcelable[] uuids) { + Log.d(LOGTAG, "UUIDs found, device: " + device); + if (uuids != null) { + ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length]; + for (int i = 0; i < uuidsToSend.length; i++) { + uuidsToSend[i] = (ParcelUuid) uuids[i]; + Log.d(LOGTAG,"index=" + i + "uuid=" + uuidsToSend[i]); + } + checkIfProfileSupported(device, uuidsToSend); + } + } + + private void checkIfProfileSupported(BluetoothDevice device, ParcelUuid[] remoteDeviceUuids) { + ParcelUuid ADV_AUDIO_T_MEDIA = + ParcelUuid.fromString("00006AD0-0000-1000-8000-00805F9B34FB"); + + ParcelUuid HEARINGAID_ADV_AUDIO = + ParcelUuid.fromString("00006AD2-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_P_MEDIA = + ParcelUuid.fromString("00006AD1-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_P_VOICE = + ParcelUuid.fromString("00006AD4-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_T_VOICE = + ParcelUuid.fromString("00006AD5-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_G_MEDIA = + ParcelUuid.fromString("12994B7E-6d47-4215-8C9E-AAE9A1095BA3"); + + ParcelUuid ADV_AUDIO_W_RECORDING = + ParcelUuid.fromString("2587DB3C-CE70-4FC9-935F-777AB4188FD7"); + + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HFP)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.HFP); + } + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.A2DP_SINK)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.A2DP); + } + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HEARING_AID)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.HAP_BREDR); + } + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.AVRCP_CONTROLLER)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.AVRCP); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_T_MEDIA)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.TMAP_MEDIA); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_T_VOICE)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.TMAP_CALL); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_P_MEDIA)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.BAP_MEDIA); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_P_VOICE)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.BAP_CALL); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_W_RECORDING)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.BAP_RECORDING); + } + } + + private void updateSupportedProfileMap(BluetoothDevice device, int mProfile) { + synchronized (mLock) { + int mSupportedProfileBitMap = 0; + if(!mSupportedProfileMap.containsKey(device)) { + //device is not added in supported map, add to it + Log.d(LOGTAG, "updateSupportedProfileMap: device " + device + + "is not added in supported map, add it"); + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } else { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + Log.d(LOGTAG, "updateSupportedProfileMap: device " + device + " for profile " + + mProfile + " mSupportedProfileBitMap " + mSupportedProfileBitMap); + mSupportedProfileBitMap = mSupportedProfileBitMap | mProfile; + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + Log.d(LOGTAG, "updateSupportedProfileMap: device " + device + " for profile " + + mProfile + " mSupportedProfileBitMap " + mSupportedProfileBitMap); + } + } + + public int getAllSupportedProfile(BluetoothDevice device) { + int mSupportedProfileBitMap = 0; + synchronized (mLock) { + if(!mSupportedProfileMap.containsKey(device)) { + Log.d(LOGTAG, "No Supported Profile found for the device " + device); + } else { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + Log.d(LOGTAG, "getAllSupportedProfile: Supported Profile for the device " + + device + " mSupportedProfileBitMap " + Integer.toHexString(mSupportedProfileBitMap)); + } + return mSupportedProfileBitMap; + } + + public int getProfile(BluetoothDevice device, Integer mAudioFeature) { + int profileMap = getSupportedProfile(device, mAudioFeature); + int preferredProfile = profileMap; + int [] mAciveProfileArray = mActiveProfileMap.get(device); + + if(profileMap == ApmConst.AudioProfiles.NONE) + return ApmConst.AudioProfiles.NONE; + + switch(mAudioFeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + int mActiveProfileForCallAudio = mAciveProfileArray[mAudioFeature]; + if(mActiveProfileForCallAudio != ApmConst.AudioProfiles.NONE) { + //active profile is set + preferredProfile = mActiveProfileForCallAudio; + return preferredProfile; + } + + if(profileMap == ApmConst.AudioProfiles.HAP_BREDR || + profileMap == ApmConst.AudioProfiles.HAP_LE) { + preferredProfile = profileMap; + return preferredProfile; + } + + int mHFP = profileMap & ApmConst.AudioProfiles.HFP; + int mLeCall = profileMap & ApmConst.AudioProfiles.TMAP_CALL; + if(mLeCall == ApmConst.AudioProfiles.NONE) + mLeCall = profileMap & ApmConst.AudioProfiles.BAP_CALL; + + if(mHFP != ApmConst.AudioProfiles.NONE && + mLeCall != ApmConst.AudioProfiles.NONE) { + preferredProfile = mPreferredProfileList[mAudioFeature]; + } else if(mHFP != ApmConst.AudioProfiles.NONE) { + preferredProfile = mHFP; + } else { + preferredProfile = mLeCall; + } + + Log.d(LOGTAG, "getProfile: device " + device + " preferredProfile: " + + preferredProfile + " for CALL_AUDIO"); + break; + + case ApmConst.AudioFeatures.MEDIA_AUDIO: + int mActiveProfileForMediaAudio = mAciveProfileArray[mAudioFeature]; + if(mActiveProfileForMediaAudio != ApmConst.AudioProfiles.NONE) { + //active profile is set + preferredProfile = mActiveProfileForMediaAudio; + Log.d(LOGTAG, "getProfile: device " + device + " Active Profile: " + + preferredProfile + " for MEDIA_AUDIO"); + return preferredProfile; + } + + if(profileMap == ApmConst.AudioProfiles.HAP_BREDR || + profileMap == ApmConst.AudioProfiles.HAP_LE) { + preferredProfile = profileMap; + return preferredProfile; + } + + int mA2dp = profileMap & ApmConst.AudioProfiles.A2DP; + int mLeMedia = profileMap & ApmConst.AudioProfiles.TMAP_MEDIA; + if(mLeMedia == ApmConst.AudioProfiles.NONE) + mLeMedia = profileMap & ApmConst.AudioProfiles.BAP_MEDIA; + + if(mA2dp != ApmConst.AudioProfiles.NONE && + mLeMedia != ApmConst.AudioProfiles.NONE) { + preferredProfile = mPreferredProfileList[mAudioFeature]; + } else if(mA2dp != ApmConst.AudioProfiles.NONE) { + preferredProfile = mA2dp; + } else { + if((preferredProfile & ApmConst.AudioProfiles.BAP_RECORDING) + != ApmConst.AudioProfiles.NONE) + preferredProfile = mPreferredProfileList[mAudioFeature]; + else + preferredProfile = mLeMedia; + } + + Log.d(LOGTAG, "getProfile: device " + device + " preferredProfile: " + + preferredProfile + " for MEDIA_AUDIO"); + break; + } + return preferredProfile; + } + + public int getSupportedProfile(BluetoothDevice device, Integer mAudioFeature) { + int [] mAciveProfileArray; + + Log.d(LOGTAG, "getSupportedProfile: for the device " + device + " AudioFeature " + mAudioFeature); + if(!mActiveProfileMap.containsKey(device)) { + // intialize the active profile but map for the device + Log.d(LOGTAG, "getSupportedProfile: intialize the active profile map for the device " + device); + mAciveProfileArray = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + Arrays.fill(mAciveProfileArray, ApmConst.AudioProfiles.NONE); + mActiveProfileMap.put(device, mAciveProfileArray); + } else { + mAciveProfileArray = mActiveProfileMap.get(device); + } + //get the supprted profile list + int mSupportedProfileBitMap = 0; + if(!mSupportedProfileMap.containsKey(device)) { + //device is not added in supported map, add to it + Log.d(LOGTAG, "getSupportedProfile: device " + device + " is not added in supported map, add it"); + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } else { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + Log.d(LOGTAG, "getSupportedProfile: supported Profiles for the device " + device + + " val = " + Integer.toHexString(mSupportedProfileBitMap)); + + switch(mAudioFeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + { + int mIsHapBREDRSupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_BREDR; + int mIsHapLESupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_LE; + + if(mIsHapBREDRSupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_BREDR; + } else if (mIsHapLESupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_LE; + } + + int mCallAudioProfile = mSupportedProfileBitMap & ( ApmConst.AudioProfiles.HFP + | ApmConst.AudioProfiles.BAP_CALL + | ApmConst.AudioProfiles.TMAP_CALL); + + Log.d(LOGTAG, "getSupportedProfile: device " + device + " supports: " + + mCallAudioProfile + " for CALL_AUDIO"); + + return mCallAudioProfile; + } + + case ApmConst.AudioFeatures.MEDIA_AUDIO: + { + int mIsHapBREDRSupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_BREDR; + int mIsHapLESupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_LE; + + if(mIsHapBREDRSupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_BREDR; + } else if (mIsHapLESupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_LE; + } + + int mMediaAudioProfile = mSupportedProfileBitMap & ( ApmConst.AudioProfiles.A2DP + | ApmConst.AudioProfiles.BAP_MEDIA + | ApmConst.AudioProfiles.TMAP_MEDIA + | ApmConst.AudioProfiles.BAP_RECORDING); + + Log.d(LOGTAG, "getSupportedProfile: device " + device + " supports: " + + mMediaAudioProfile + " for MEDIA_AUDIO"); + + return mMediaAudioProfile; + } + case ApmConst.AudioFeatures.MEDIA_CONTROL: + { + int mActiveProfileForMediaControl = mAciveProfileArray + [ApmConst.AudioFeatures.MEDIA_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " ActiveProfile For MediaControl " + + mActiveProfileForMediaControl); + return mActiveProfileForMediaControl; + } + case ApmConst.AudioFeatures.CALL_CONTROL: + { + int mActiveProfileForCallControl = mAciveProfileArray + [ApmConst.AudioFeatures.CALL_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " ActiveProfile For Call Control " + + mActiveProfileForCallControl); + return mActiveProfileForCallControl; + } + case ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL: + { + int mActiveProfileForMediaVolControl = mAciveProfileArray + [ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + + " ActiveProfile For Media Volume Control " + mActiveProfileForMediaVolControl); + + return mActiveProfileForMediaVolControl; + } + case ApmConst.AudioFeatures.CALL_VOLUME_CONTROL: + { + int mActiveProfileForCallVolControl = mAciveProfileArray + [ApmConst.AudioFeatures.CALL_VOLUME_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + + " ActiveProfile For call Volume Control " + mActiveProfileForCallVolControl); + + return mActiveProfileForCallVolControl; + + } + case ApmConst.AudioFeatures.BROADCAST_AUDIO: + { + int mActiveProfileForBroadCastAudio = mAciveProfileArray + [ApmConst.AudioFeatures.BROADCAST_AUDIO]; + if(mActiveProfileForBroadCastAudio != ApmConst.AudioProfiles.NONE) { + //active profile is set + return mActiveProfileForBroadCastAudio; + } + + int mIsBroadCastBREDRSupported = mSupportedProfileBitMap & + ApmConst.AudioProfiles.BROADCAST_BREDR; + int mIsBroadCastLESupported = + mSupportedProfileBitMap & ApmConst.AudioProfiles.BROADCAST_LE; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " mIsBroadCastBREDRSupported " + + mIsBroadCastBREDRSupported + " mIsBroadCastLESupported " + + mIsBroadCastLESupported); + + if((mIsBroadCastBREDRSupported != 0) && (mIsBroadCastLESupported != 0)) { + return mPreferredProfileList[ApmConst.AudioFeatures.BROADCAST_AUDIO]; + } else if (mIsBroadCastBREDRSupported != 0) { + return ApmConst.AudioProfiles.BROADCAST_BREDR; + } else if(mIsBroadCastLESupported != 0) { + return ApmConst.AudioProfiles.BROADCAST_LE; + } + break; + } + case ApmConst.AudioFeatures.HEARING_AID: + { + int mActiveProfileForHearingAid = mAciveProfileArray + [ApmConst.AudioFeatures.HEARING_AID]; + if(mActiveProfileForHearingAid != ApmConst.AudioProfiles.NONE) { + //active profile is set + return mActiveProfileForHearingAid; + } + + int mIsHAPBREDRSupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_BREDR; + int mIsHAPLESupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_LE; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " mIsHAPBREDRSupported " + + mIsHAPBREDRSupported + " mIsHAPLESupported " + mIsHAPLESupported); + + if((mIsHAPBREDRSupported != 0) && (mIsHAPLESupported !=0)) { + return mPreferredProfileList[ApmConst.AudioFeatures.HEARING_AID]; + } else if (mIsHAPBREDRSupported != 0) { + return ApmConst.AudioProfiles.HAP_BREDR; + } else if(mIsHAPLESupported != 0) { + return ApmConst.AudioProfiles.HAP_LE; + } + break; + } + default : + { + Log.w(LOGTAG, "getSupportedProfile: no profile supported for" + + mAudioFeature + " device " + device); + return ApmConst.AudioProfiles.NONE; + } + } + return ApmConst.AudioProfiles.NONE; + } + + public void profileDescoveryUpdate (BluetoothDevice device, Integer mAudioProfile) { + int mSupportedProfileBitMap = 0; + if(mSupportedProfileMap.containsKey(device)) { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + + mSupportedProfileBitMap = mSupportedProfileBitMap | mAudioProfile; + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } + + public void profileConnectionUpdate(BluetoothDevice device, Integer mAudioFeature, + Integer mAudioProfile, Boolean mProfileStatus) { + int mSupportedProfileBitMap = 0; + int mConnectedProfileBitMap = 0; + int [] mAciveProfileArray; + + Log.d(LOGTAG, "profileConnectionUpdate: device : " + device + " AudioProfile " + + mAudioProfile + " ProfileStatus " + mProfileStatus); + + synchronized (mLock) { + // get the Connected profile list + if(mConnectedProfileMap.containsKey(device)) { + mConnectedProfileBitMap = mConnectedProfileMap.get(device); + } + + // get the Supported profile list + if(mSupportedProfileMap.containsKey(device)) { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + + if(!mActiveProfileMap.containsKey(device)) { + // intialize the active profile but map for the device + Log.d(LOGTAG, "profileConnectionUpdate: intialize the active " + + " profile map for the device " + device); + mAciveProfileArray = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + Arrays.fill(mAciveProfileArray, ApmConst.AudioProfiles.NONE); + mActiveProfileMap.put(device, mAciveProfileArray); + } else { + mAciveProfileArray = mActiveProfileMap.get(device); + } + // update the Audio profile connection in list + if (mProfileStatus) { + mSupportedProfileBitMap = mSupportedProfileBitMap | mAudioProfile; + mConnectedProfileBitMap = mConnectedProfileBitMap | mAudioProfile; + + //update the active profile list + if(mAciveProfileArray[mAudioFeature] == ApmConst.AudioProfiles.NONE) { + mAciveProfileArray[mAudioFeature] = mAudioProfile; + } else if(mAciveProfileArray[mAudioFeature] != mAudioProfile) { + // diffrent profile connected for the same mAudioFeature need to update the active list. + int mPreferredProfile = mPreferredProfileList[mAudioFeature]; + Log.d(LOGTAG, "profileConnectionUpdate: PreferredProfile for audio feature " + + mAudioFeature + " is " + mPreferredProfile + " device " + device); + if((mPreferredProfile != ApmConst.AudioProfiles.NONE) + && (mConnectedProfileBitMap & mPreferredProfile) != 0) { + mAciveProfileArray[mAudioFeature] = mPreferredProfile; + } + } + } else { + mConnectedProfileBitMap = mConnectedProfileBitMap & ~mAudioProfile; + // profile disconnect for active profile + if(mAciveProfileArray[mAudioFeature] == mAudioProfile) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + // do we need to update the active profile with other connected profile for that audio feature. + switch(mAudioFeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + if(mAudioProfile == ApmConst.AudioProfiles.HFP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.TMAP_CALL) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.TMAP_CALL; + } else if ((mConnectedProfileBitMap & ApmConst.AudioProfiles.BAP_CALL) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.BAP_CALL; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.TMAP_CALL + || mAudioProfile == ApmConst.AudioProfiles.BAP_CALL) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.HFP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.HFP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.MEDIA_AUDIO: + if(mAudioProfile == ApmConst.AudioProfiles.A2DP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.TMAP_MEDIA) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.TMAP_MEDIA; + } else if ((mConnectedProfileBitMap & ApmConst.AudioProfiles.BAP_MEDIA) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.BAP_MEDIA; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.TMAP_MEDIA + || mAudioProfile == ApmConst.AudioProfiles.BAP_MEDIA) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.A2DP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.A2DP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.CALL_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.HFP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.CCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.CCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.CCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.HFP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.HFP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.MEDIA_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.AVRCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.MCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.MCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.MCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.AVRCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.AVRCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.AVRCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.VCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.VCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.VCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.AVRCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.AVRCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.CALL_VOLUME_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.HFP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.VCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.VCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.VCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.HFP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.HFP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + default: + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + } + + mActiveProfileMap.put(device, mAciveProfileArray); + Log.d(LOGTAG, "profileConnectionUpdate: supported Profiles for the device " + device + + " val " + Integer.toHexString(mSupportedProfileBitMap)); + Log.d(LOGTAG, "profileConnectionUpdate: connected Profiles for the device " + device + + " val " + Integer.toHexString(mConnectedProfileBitMap)); + + mConnectedProfileMap.put(device, mConnectedProfileBitMap); + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } + } + + public boolean isProfileConnected(BluetoothDevice device, Integer mAudioProfile) { + if (device == null) return false; + int mConnectedProfileBitMap = mConnectedProfileMap.get(device); + Log.d(LOGTAG, "isProfileConnected: device: " + device + + " mAudioProfile: " + mAudioProfile + " mConnectedProfileMap: " + mConnectedProfileBitMap); + + return ((mConnectedProfileBitMap & mAudioProfile) == mAudioProfile); + } + + public void setActiveProfile(BluetoothDevice device, Integer mAudioFeature, Integer mAudioProfile) { + int [] mAciveProfileArray; + Log.d(LOGTAG, "setActiveProfile: device : " + device + " AudioProfile " + + mAudioProfile + " AudioFeature " + mAudioFeature); + synchronized (mLock) { + if(!mActiveProfileMap.containsKey(device)) { + Log.d(LOGTAG, "setActiveProfile: intialize the active profile map for the device " + + device); + mAciveProfileArray = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + Arrays.fill(mAciveProfileArray, ApmConst.AudioProfiles.NONE); + mActiveProfileMap.put(device, mAciveProfileArray); + } else { + mAciveProfileArray = mActiveProfileMap.get(device); + } + mAciveProfileArray[mAudioFeature] = mAudioProfile; + mActiveProfileMap.put(device, mAciveProfileArray); + } + } + + static int getLeMediaProfiles() { + return LeMediaProfiles; + } + + static int getLeCallProfiles() { + return LeCallProfiles; + } + + public synchronized void handleDeviceShutdown() { + Log.d(LOGTAG, "handleDeviceShutdown: started"); + + //store the supported profiles for the bonded devices + SharedPreferences.Editor pref = getSupportedProfileMap().edit(); + for (BluetoothDevice mBluetoothDevice : mSupportedProfileMap.keySet()) { + int mSupportedProfilesVal = mSupportedProfileMap.get(mBluetoothDevice); + Log.d(LOGTAG, "cleanup: supported Profiles for the device " + mBluetoothDevice + + " val = " + Integer.toHexString(mSupportedProfilesVal)); + if(mBluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + pref.putInt(mBluetoothDevice.getAddress(), mSupportedProfilesVal); + } + } + pref.apply(); + mSupportedProfileMap.clear(); + mActiveProfileMap.clear(); + mConnectedProfileMap.clear(); + + Log.d(LOGTAG, "handleDeviceShutdown: Done"); + } + + public synchronized void cleanup () { + if(DPMSingleInstance == null) { + Log.w(LOGTAG, "cleanup called without initialization, Returning"); + return; + } + + Log.d(LOGTAG, "cleanup: started"); + + //store the supported profiles for the bonded devices + SharedPreferences.Editor pref = getSupportedProfileMap().edit(); + for (BluetoothDevice mBluetoothDevice : mSupportedProfileMap.keySet()) { + int mSupportedProfilesVal = mSupportedProfileMap.get(mBluetoothDevice); + Log.d(LOGTAG, "cleanup: supported Profiles for the device " + mBluetoothDevice + + " val = " + Integer.toHexString(mSupportedProfilesVal)); + if(mBluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + pref.putInt(mBluetoothDevice.getAddress(), mSupportedProfilesVal); + } + } + pref.apply(); + mSupportedProfileMap.clear(); + mActiveProfileMap.clear(); + mConnectedProfileMap.clear(); + DPMSingleInstance = null; + mContext.unregisterReceiver(mDeviceProfileMapReceiver); + + Log.d(LOGTAG, "cleanup: Done"); + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java new file mode 100644 index 0000000000000000000000000000000000000000..057c0ebdf73c3ca73fda5bf6755c739031ed468a --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java @@ -0,0 +1,1204 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.IBluetoothA2dp; + +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ActiveDeviceManager; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.a2dp.A2dpService; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.broadcast.BroadcastService; +import com.android.bluetooth.acm.AcmService; +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.util.Log; +import android.util.StatsLog; +import com.android.bluetooth.hfp.HeadsetService; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.SystemProperties; + +public class MediaAudio { + private static MediaAudio sMediaAudio; + private AdapterService mAdapterService; +// private BapBroadcastService mBapBroadcastService; + private ActiveDeviceManagerService mActiveDeviceManager; + private Context mContext; + BapBroadcastManager mBapBroadcastManager; + Map mMediaDevices; + + final ArrayList supported_codec = new ArrayList( List.of( + "LC3" + )); + + private BroadcastReceiver mCodecConfigReceiver; + private BroadcastReceiver mQosConfigReceiver; + public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + public static final String BLUETOOTH_PRIVILEGED = + android.Manifest.permission.BLUETOOTH_PRIVILEGED; + public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + public static final String ACTION_UPDATE_CODEC_CONFIG = + "qti.intent.bluetooth.action.UPDATE_CODEC_CONFIG"; + public static final String CODEC_ID = + "qti.bluetooth.extra.CODEC_ID"; + public static final String CODEC_CONFIG = + "qti.bluetooth.extra.CODEC_CONFIG"; + public static final String CHANNEL_MODE = + "qti.bluetooth.extra.CHANNEL_MODE"; + public static final String ACTION_UPDATE_QOS_CONFIG = + "qti.intent.bluetooth.action.UPDATE_QOS_CONFIG"; + public static final String QOS_CONFIG = + "qti.bluetooth.extra.QOS_CONFIG"; + private static final int MAX_DEVICES = 200; + public static final String TAG = "APM: MediaAudio"; + public static final boolean DBG = true; + + private static final long AUDIO_RECORDING_MASK = 0x00030000; + private static final long AUDIO_RECORDING_OFF = 0x00010000; + private static final long AUDIO_RECORDING_ON = 0x00020000; + + private static final long GAMING_OFF = 0x00001000; + private static final long GAMING_ON = 0x00002000; + private static final long GAMING_MODE_MASK = 0x00007000; + + private static boolean mIsRecordingEnabled; + + private AudioManager mAudioManager; + + private MediaAudio(Context context) { + Log.i(TAG, "initialization"); + + mContext = context; + mMediaDevices = new ConcurrentHashMap(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when A2dpService starts"); + + mAdapterService = AdapterService.getAdapterService(); + mActiveDeviceManager = ActiveDeviceManagerService.get(); + + mBapBroadcastManager = new BapBroadcastManager(); + + IntentFilter codecFilter = new IntentFilter(); + codecFilter.addAction(ACTION_UPDATE_CODEC_CONFIG); + mCodecConfigReceiver = new LeCodecConfig(); + context.registerReceiver(mCodecConfigReceiver, codecFilter); + + IntentFilter qosFilter = new IntentFilter(); + qosFilter.addAction(ACTION_UPDATE_QOS_CONFIG); + mQosConfigReceiver = new QosConfigReceiver(); + context.registerReceiver(mQosConfigReceiver, qosFilter); + + mIsRecordingEnabled = + SystemProperties.getBoolean("persist.vendor.service.bt.recording_supported", false); + + //2 Setup Codec Config here + } + + public static MediaAudio init(Context context) { + if(sMediaAudio == null) { + sMediaAudio = new MediaAudio(context); + MediaAudioIntf.init(sMediaAudio); + } + return sMediaAudio; + } + + public static MediaAudio get() { + return sMediaAudio; + } + + public boolean connect(BluetoothDevice device) { + return connect (device, false, false); + } + + public boolean connect(BluetoothDevice device, Boolean allProfile) { + return connect (device, allProfile, false); + } + + public boolean autoConnect(BluetoothDevice device) { + Log.e(TAG, "autoConnect: " + device); + return connect(device, false, true); + } + + private boolean connect(BluetoothDevice device, boolean allProfile, boolean autoConnect) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + + Log.e(TAG, "connect: " + device + " allProfile: " + allProfile + + " autoConnect: " + autoConnect); + if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); + return false; + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (dMap == null) + return false; + + int peer_supported_profiles = dMap.getAllSupportedProfile(device); + boolean is_peer_support_recording = + ((peer_supported_profiles & ApmConst.AudioProfiles.BAP_RECORDING) != 0); + int profileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO); + if (profileID == ApmConst.AudioProfiles.NONE) { + Log.e(TAG, "Can Not connect to " + device + ". Device does not support media service."); + return false; + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + if(mMediaDevices.size() >= MAX_DEVICES) + return false; + mMediaDevice = new MediaDevice(device, profileID); + mMediaDevices.put(device.getAddress(), mMediaDevice); + } else if(mMediaDevice.deviceConnStatus != BluetoothProfile.STATE_DISCONNECTED) { + Log.i(TAG, "Device already connected"); + return false; + } + + if((ApmConst.AudioProfiles.A2DP & profileID) == ApmConst.AudioProfiles.A2DP) { + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + service.connectA2dp(device); + } + } + + BluetoothDevice groupDevice = device; + StreamAudioService mStreamService = StreamAudioService.getStreamAudioService(); + + if(mStreamService != null && + (ApmConst.AudioProfiles.BAP_MEDIA & profileID) == ApmConst.AudioProfiles.BAP_MEDIA) { + int defaultMediaProfile = ApmConst.AudioProfiles.BAP_MEDIA; + + /* handle common conect of call and media audio */ + if(autoConnect) { + groupDevice = mStreamService.getDeviceGroup(device); + Log.i(TAG, "Auto Connect Request. Connecting group: " + groupDevice); + } + + /*int defaultMusicProfile = dMap.getProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO); + if((ApmConst.AudioProfiles.A2DP & defaultMusicProfile) == ApmConst.AudioProfiles.A2DP) { + Log.i(TAG, "A2DP is default profile for Music, configure BAP for Gaming"); + defaultMediaProfile = ApmConst.AudioProfiles.BAP_GCP; + }*/ + + if(mIsRecordingEnabled) { + Log.i(TAG, "Add Recording profile to LE connect request"); + defaultMediaProfile = defaultMediaProfile | ApmConst.AudioProfiles.BAP_RECORDING; + } + + if(allProfile) { + int callProfileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if((callProfileID & ApmConst.AudioProfiles.BAP_CALL) == ApmConst.AudioProfiles.BAP_CALL) { + Log.i(TAG, "Add BAP_CALL to LE connect request"); + mStreamService.connectLeStream(groupDevice, + defaultMediaProfile | ApmConst.AudioProfiles.BAP_CALL); + } else { + mStreamService.connectLeStream(groupDevice, defaultMediaProfile); + } + } else { + mStreamService.connectLeStream(groupDevice, defaultMediaProfile); + } + } + return true; + } + + public boolean disconnect(BluetoothDevice device) { + return disconnect(device, false); + } + + public boolean disconnect(BluetoothDevice device, Boolean allProfile) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); + Log.i(TAG, "Disconnect: " + device); + MediaDevice mMediaDevice = null; + + if(device == null) + return false; + + mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + Log.e(TAG, "Ignore: Device " + device + " not present in list"); + return false; + } + + if (mMediaDevice.profileConnStatus[MediaDevice.A2DP_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + service.disconnectA2dp(device); + } + } + if (mMediaDevice.profileConnStatus[MediaDevice.LE_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (!dMap.isProfileConnected(device, ApmConst.AudioProfiles.BAP_CALL)) { + Log.d(TAG,"BAP_CALL not connected"); + allProfile = false; + } + service.disconnectLeStream(device, allProfile, true); + } + } + + return true; + } + + public List getConnectedDevices() { + Log.i(TAG, "getConnectedDevices: "); + if(mMediaDevices.size() == 0) { + return new ArrayList<>(0); + } + + List connectedDevices = new ArrayList<>(); + for(MediaDevice mMediaDevice : mMediaDevices.values()) { + if(mMediaDevice.deviceConnStatus == BluetoothProfile.STATE_CONNECTED) { + connectedDevices.add(mMediaDevice.mDevice); + } + } + return connectedDevices; + } + + public List getDevicesMatchingConnectionStates(Integer[] states) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + + Log.i(TAG, "getDevicesMatchingConnectionStates: "); + List devices = new ArrayList<>(); + if (states == null) { + return devices; + } + + BluetoothDevice [] bondedDevices = null; + bondedDevices = mAdapterService.getBondedDevices(); + if(bondedDevices == null) { + return devices; + } + + for (BluetoothDevice device : bondedDevices) { + MediaDevice mMediaDevice; + int state = BluetoothProfile.STATE_DISCONNECTED; + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if(dMap == null) { + return new ArrayList<>(0); + } + if(dMap.getProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO) == ApmConst.AudioProfiles.NONE) { + continue; + } + + mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice != null) + state = mMediaDevice.deviceConnStatus; + + for(int s: states) { + if(s == state) { + devices.add(device); + break; + } + } + } + return devices; + } + + public int getConnectionState(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + + if(device == null) + return BluetoothProfile.STATE_DISCONNECTED; + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice != null) + return mMediaDevice.deviceConnStatus; + + return BluetoothProfile.STATE_DISCONNECTED; + } + + public int getPriority(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(mAdapterService != null) { + return mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.A2DP); + } + return BluetoothProfile.PRIORITY_UNDEFINED; + } + + public boolean isA2dpPlaying(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + + if(device == null) + return false; + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice != null) { + Log.i(TAG, "isA2dpPlaying: " + mMediaDevice.streamStatus); + return (mMediaDevice.streamStatus == BluetoothA2dp.STATE_PLAYING); + } + + return false; + } + + public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + + Log.i(TAG, "getCodecStatus: for device: " + device); + if(device == null) + return null; + + if (mBapBroadcastManager.isBapBroadcastActive()) { + return mBapBroadcastManager.getCodecStatus(); + } + + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + int profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + if(profile != ApmConst.AudioProfiles.NONE && profile != ApmConst.AudioProfiles.A2DP) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + device = service.getDeviceGroup(device); + } + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + Log.i(TAG, "getCodecStatus: for mMediaDevice: " + mMediaDevice); + if(mMediaDevice == null) + return null; + + Log.i(TAG, "getCodecStatus: " + mMediaDevice.mCodecStatus); + return mMediaDevice.mCodecStatus; + } + + public void setCodecConfigPreference(BluetoothDevice mDevice, + BluetoothCodecConfig codecConfig) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + BluetoothDevice device = mDevice; + + Log.i(TAG, "setCodecConfigPreference: " + codecConfig); + if(device == null) { + if(mActiveDeviceManager != null) { + device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + if(device == null) + return; + + if (codecConfig == null) { + Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); + return; + } + long cs4 = codecConfig.getCodecSpecific4(); + + if (mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO) == + ApmConst.AudioProfiles.BROADCAST_LE) { + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mPrevDevice = mActiveDeviceManager.getQueuedDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (mPrevDevice != null && mAcmService != null) { + if ((cs4 & AUDIO_RECORDING_MASK) == AUDIO_RECORDING_ON) { + if (mAcmService.getConnectionState(mPrevDevice) == BluetoothProfile.STATE_CONNECTED) { + device = mPrevDevice; + Log.d(TAG,"Recording request, switch device to " + device); + } else { + Log.d(TAG,"Not DUMO device, ignore recording request"); + return; + } + } + } else if ((cs4 & GAMING_MODE_MASK) == GAMING_ON) { + Log.d(TAG, "Ignore gaming mode request when broadcast is active"); + return; + } + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + int supported_prfiles = dMap.getAllSupportedProfile(device); + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + boolean peer_supports_recording = + ((supported_prfiles & ApmConst.AudioProfiles.BAP_RECORDING) != 0); + + int profileIndex = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.BAP_MEDIA); + int profileIndexA2dp = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.A2DP); + boolean is_peer_connected_for_recording = + (mMediaDevice.profileConnStatus[profileIndex] == + BluetoothProfile.STATE_CONNECTED); + boolean is_peer_connected_for_a2dp = (mMediaDevice.profileConnStatus[profileIndexA2dp] == + BluetoothProfile.STATE_CONNECTED); + Log.i(TAG, "is_peer_connected_for_recording: " + is_peer_connected_for_recording + + ", is_peer_connected_for_a2dp: " + is_peer_connected_for_a2dp); + CallAudio mCallAudio = CallAudio.get(); + boolean isInCall = mCallAudio != null && mCallAudio.isVoiceOrCallActive(); + // TODO : check the FM related rx activity + if (mActiveDeviceManager != null && + peer_supports_recording && mIsRecordingEnabled && + is_peer_connected_for_recording && is_peer_connected_for_a2dp) { + if ((cs4 & AUDIO_RECORDING_MASK) == AUDIO_RECORDING_ON) { + if(!isInCall && + !mActiveDeviceManager.isRecordingActive(device)) { + mActiveDeviceManager.enableRecording(device); + } + + } else if ((cs4 & AUDIO_RECORDING_MASK) == AUDIO_RECORDING_OFF) { + if(mActiveDeviceManager.isRecordingActive(device)) { + mActiveDeviceManager.disableRecording(device); + } + } + } + + boolean isBapConnected = (mMediaDevice.profileConnStatus[mMediaDevice.LE_STREAM] + == BluetoothProfile.STATE_CONNECTED); + + if(isBapConnected) { + long mGamingStatus = (cs4 & GAMING_MODE_MASK); + if((mGamingStatus & GAMING_ON) > 0) { + Log.w(TAG, "Turning On Gaming Mode"); + mActiveDeviceManager.enableGaming(device); + return; + } else if((mGamingStatus & GAMING_OFF) > 0) { + Log.w(TAG, "Turning Off Gaming Mode"); + mActiveDeviceManager.disableGaming(device); + return; + } + } + + int profileID = dMap.getProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO); + + if(ApmConst.AudioProfiles.A2DP == profileID) { + if(codecConfig.getCodecType() == + BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { + return; + } + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + service.setCodecConfigPreferenceA2dp(device, codecConfig); + return; + } + }/* else if(ApmConst.AudioProfiles.BAP == profileID) { // once implemented + LeStreamService service = LeStreamService.getLeStreamService(); + if(service != null) { + service.setCodecConfigPreferenceLeStream(device, codecConfig); + return; + } + }*/ + + } + + public void enableOptionalCodecs(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Log.i(TAG, "enableOptionalCodecs: "); + + BluetoothCodecStatus mCodecStatus = null; + + if (device == null) { + if(mActiveDeviceManager != null) { + device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + if (device == null) { + Log.e(TAG, "enableOptionalCodecs: Invalid device"); + return; + } + + if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { + Log.e(TAG, "enableOptionalCodecs: No optional codecs"); + return; + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + int profileIndex = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.A2DP); + mCodecStatus = mMediaDevice.mProfileCodecStatus[profileIndex]; + if(mCodecStatus != null) { + service.enableOptionalCodecsA2dp(device, mCodecStatus.getCodecConfig()); + } + } + // 2 Should implement common codec handling when + //vendor codecs is introduced in LE Audio + } + + public void disableOptionalCodecs(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Log.i(TAG, "disableOptionalCodecs: "); + BluetoothCodecStatus mCodecStatus = null; + if (device == null) { + if(mActiveDeviceManager != null) { + device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + if (device == null) { + Log.e(TAG, "disableOptionalCodecs: Invalid device"); + return; + } + + if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { + Log.e(TAG, "disableOptionalCodecs: No optional codecs"); + return; + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + int profileIndex = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.A2DP); + mCodecStatus = mMediaDevice.mProfileCodecStatus[profileIndex]; + if(mCodecStatus != null) { + service.disableOptionalCodecsA2dp(device, mCodecStatus.getCodecConfig()); + } + } + // 2 Should implement common codec handling when + //vendor codecs is introduced in LE Audio + } + + public int getSupportsOptionalCodecs(BluetoothDevice device) { + if(mAdapterService != null) + return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device); + return BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED; + } + + public int supportsOptionalCodecs(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(mAdapterService.isTwsPlusDevice(device)) { + Log.w(TAG, "Disable optional codec support for TWS+ device"); + return BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED; + } + return getSupportsOptionalCodecs(device); + } + + public int getOptionalCodecsEnabled(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(mAdapterService != null) + return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device); + return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; + } + + public void setOptionalCodecsEnabled(BluetoothDevice device, Integer value) { + Log.i(TAG, "setOptionalCodecsEnabled: " + value); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN + && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED + && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { + Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value); + return; + } + + if(mAdapterService != null) + mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value); + } + + public int getConnectionPolicy(BluetoothDevice device) { + if(mAdapterService != null) + return mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.A2DP); + return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + } + + public boolean setConnectionPolicy(BluetoothDevice device, Integer connectionPolicy) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, + "Need BLUETOOTH_PRIVILEGED permission"); + if (DBG) { + Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); + } + boolean setSuccessfully; + setSuccessfully = mAdapterService.getDatabase() + .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, connectionPolicy); + + if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + connect(device); + } else if (setSuccessfully + && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + disconnect(device); + } + return setSuccessfully; + } + + public boolean setSilenceMode(BluetoothDevice device, Boolean silence) { + if (DBG) { + Log.d(TAG, "setSilenceMode(" + device + "): " + silence); + } + BluetoothDevice mActiveDevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (silence && Objects.equals(mActiveDevice, device)) { + mActiveDeviceManager.removeActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO, true); + } else if (!silence && null == + mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO)) { + // Set the device as the active device if currently no active device. + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.MEDIA_AUDIO, false); + } + return true; + } + + public void onConnStateChange(BluetoothDevice device, Integer state, Integer profile) { + Log.d(TAG, "onConnStateChange: profile: " + profile + " state: " + state + " for device " + device); + if(device == null) + return; + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + + if(mMediaDevice == null) { + if(state == BluetoothProfile.STATE_DISCONNECTED) + return; + if(mMediaDevices.size() >= MAX_DEVICES) { + return; + } + mMediaDevice = new MediaDevice(device, profile, state); + mMediaDevices.put(device.getAddress(), mMediaDevice); + broadcastConnStateChange(device, BluetoothProfile.STATE_DISCONNECTED, state); + return; + } + + int profileIndex = mMediaDevice.getProfileIndex(profile); + int prevState = mMediaDevice.deviceConnStatus; + if(mMediaDevice.profileConnStatus[profileIndex] == state) { + Log.w(TAG, "Profile already in state: " + state + ". Return"); + return; + } + mMediaDevice.profileConnStatus[profileIndex] = state; + + if(state == BluetoothProfile.STATE_CONNECTED) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.MEDIA_AUDIO, profile, true); + refreshCurrentCodec(device); + } else if(state == BluetoothProfile.STATE_DISCONNECTED) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.MEDIA_AUDIO, profile, false); + } + + int otherProfileConnectionState = mMediaDevice.profileConnStatus[(profileIndex+1)%2]; + Log.w(TAG, " otherProfileConnectionState: " + otherProfileConnectionState); + + switch(otherProfileConnectionState) { + /*Send Broadcast based on state of other profile*/ + case BluetoothProfile.STATE_DISCONNECTED: + broadcastConnStateChange(device, prevState, state); + mMediaDevice.deviceConnStatus = state; + break; + case BluetoothProfile.STATE_CONNECTING: + if(state == BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, state); + mMediaDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_DISCONNECTING: + if(state == BluetoothProfile.STATE_CONNECTING || + state == BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, state); + mMediaDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_CONNECTED: + ActiveDeviceManagerService mActiveDeviceManager = + ActiveDeviceManagerService.get(); + if(mActiveDeviceManager == null) { + break; + } + + BluetoothDevice mActiveDevice = mActiveDeviceManager + .getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + + if((state == BluetoothProfile.STATE_CONNECTED) || + (state == BluetoothProfile.STATE_DISCONNECTED && + device.equals(mActiveDevice))) { + Log.w(TAG, "onConnStateChange: Trigger Media handoff for Device: " + device); + mActiveDeviceManager.setActiveDevice(device, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + break; + } + + if (profileIndex == mMediaDevice.LE_STREAM && + state == BluetoothProfile.STATE_DISCONNECTED) { + mMediaDevice.mProfileCodecStatus[profileIndex] = null; + } + } + + public void onConnStateChange(BluetoothDevice device, int state, int profile, boolean isFirstMember) { + Log.w(TAG, "onConnStateChange: state:" + state + " for device " + device + " new group: " + isFirstMember); + if((state == BluetoothProfile.STATE_CONNECTED || state == BluetoothProfile.STATE_CONNECTING) + && isFirstMember) { + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + BluetoothDevice groupDevice = mStreamAudioService.getDeviceGroup(device); + if(groupDevice != null) { + MediaDevice mMediaDevice = mMediaDevices.get(groupDevice.getAddress()); + if(mMediaDevice == null) { + mMediaDevice = new MediaDevice(groupDevice, profile, BluetoothProfile.STATE_CONNECTED); + mMediaDevices.put(groupDevice.getAddress(), mMediaDevice); + } else { + int profileIndex = mMediaDevice.getProfileIndex(profile); + mMediaDevice.profileConnStatus[profileIndex] = BluetoothProfile.STATE_CONNECTED; + mMediaDevice.deviceConnStatus = state; + } + } + } else if(isFirstMember && (state == BluetoothProfile.STATE_DISCONNECTING || + state == BluetoothProfile.STATE_DISCONNECTED)) { + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + BluetoothDevice groupDevice = mStreamAudioService.getDeviceGroup(device); + MediaDevice mMediaDevice = mMediaDevices.get(groupDevice.getAddress()); + int prevState = BluetoothProfile.STATE_CONNECTED; + Log.w(TAG, "onConnStateChange: mMediaDevice: " + mMediaDevice); + if(mMediaDevice != null) { + prevState = mMediaDevice.deviceConnStatus; + int profileIndex = mMediaDevice.getProfileIndex(profile); + mMediaDevice.profileConnStatus[profileIndex] = state; + mMediaDevice.deviceConnStatus = state; + Log.w(TAG, "onConnStateChange: device: " + groupDevice + " state = " + mMediaDevice.deviceConnStatus); + } + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onDeviceConnStateChange(groupDevice, state, prevState, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + onConnStateChange(device, state, profile); + } + + public void onStreamStateChange(BluetoothDevice device, Integer streamStatus) { + int prevStatus; + if(device == null) + return; + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + return; + } + + if(mMediaDevice.streamStatus == streamStatus) + return; + + prevStatus = mMediaDevice.streamStatus; + mMediaDevice.streamStatus = streamStatus; + Log.d(TAG, "onStreamStateChange update to volume manager"); + VolumeManager mVolumeManager = VolumeManager.get(); + mVolumeManager.updateStreamState(device, streamStatus, ApmConst.AudioFeatures.MEDIA_AUDIO); + broadcastStreamState(device, prevStatus, streamStatus); + } + + protected BluetoothCodecStatus convergeCodecConfig(MediaDevice mMediaDevice) { + BluetoothCodecStatus A2dpCodecStatus = mMediaDevice.mProfileCodecStatus[MediaDevice.A2DP_STREAM]; + BluetoothCodecStatus BapCodecStatus = mMediaDevice.mProfileCodecStatus[MediaDevice.LE_STREAM]; + BluetoothCodecStatus mCodecStatus = null; + + if(A2dpCodecStatus == null || + mMediaDevice.profileConnStatus[MediaDevice.A2DP_STREAM] != + BluetoothProfile.STATE_CONNECTED) { + return BapCodecStatus; + } + + if(BapCodecStatus == null || + mMediaDevice.profileConnStatus[MediaDevice.LE_STREAM] != + BluetoothProfile.STATE_CONNECTED) { + return A2dpCodecStatus; + } + + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + int mActiveProfile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + int mActiveProfileIndex = mMediaDevice.getProfileIndex(mActiveProfile); + BluetoothCodecConfig mCodecConfig = mMediaDevice.mProfileCodecStatus[mActiveProfileIndex].getCodecConfig(); + + Log.d(TAG, "convergeCodecConfig: mActiveProfile: " + + mActiveProfile + ", mActiveProfileIndex: " + mActiveProfileIndex); + + BluetoothCodecConfig[] mCodecsLocalCapabilities = new BluetoothCodecConfig[ + A2dpCodecStatus.getCodecsLocalCapabilities().length + + BapCodecStatus.getCodecsLocalCapabilities().length]; + System.arraycopy(A2dpCodecStatus.getCodecsLocalCapabilities(), 0, mCodecsLocalCapabilities, 0, + A2dpCodecStatus.getCodecsLocalCapabilities().length); + System.arraycopy(BapCodecStatus.getCodecsLocalCapabilities(), 0, mCodecsLocalCapabilities, + A2dpCodecStatus.getCodecsLocalCapabilities().length, + BapCodecStatus.getCodecsLocalCapabilities().length); + + BluetoothCodecConfig[] mCodecsSelectableCapabilities = new BluetoothCodecConfig[ + A2dpCodecStatus.getCodecsSelectableCapabilities().length + + BapCodecStatus.getCodecsSelectableCapabilities().length]; + System.arraycopy(A2dpCodecStatus.getCodecsSelectableCapabilities(), 0, mCodecsSelectableCapabilities, 0, + A2dpCodecStatus.getCodecsSelectableCapabilities().length); + System.arraycopy(BapCodecStatus.getCodecsSelectableCapabilities(), 0, mCodecsSelectableCapabilities, + A2dpCodecStatus.getCodecsSelectableCapabilities().length, + BapCodecStatus.getCodecsSelectableCapabilities().length); + + mCodecStatus = new BluetoothCodecStatus(mCodecConfig, + mCodecsLocalCapabilities, mCodecsSelectableCapabilities); + return mCodecStatus; + } + + public void onCodecConfigChange(BluetoothDevice device, BluetoothCodecStatus mCodecStatus, Integer profile) { + onCodecConfigChange(device, mCodecStatus, profile, true); + } + + protected void refreshCurrentCodec(BluetoothDevice device) { + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + return; + } + + mMediaDevice.mCodecStatus = convergeCodecConfig(mMediaDevice); + + Log.d(TAG, "refreshCurrentCodec: " + device + ", " + mMediaDevice.mCodecStatus); + + broadcastCodecStatus(device, mMediaDevice.mCodecStatus); + } + + public void onCodecConfigChange(BluetoothDevice device, + BluetoothCodecStatus codecStatus, Integer profile, Boolean updateAudio) { + Log.w(TAG, "onCodecConfigChange: for profile:" + profile + " for device " + + device + " update audio: " + updateAudio + " with status " + codecStatus); + if(device == null || codecStatus == null) + return; + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + BluetoothCodecStatus prevCodecStatus = null; + //BapBroadcastService mBapBroadcastService = BapBroadcastService.getBapBroadcastService(); + if (mMediaDevice == null && profile == ApmConst.AudioProfiles.BROADCAST_LE) { + Log.d(TAG,"LE Broadcast codec change"); + } else if(mMediaDevice == null) { + Log.e(TAG, "No entry in Device Profile map for device: " + device); + return; + } + if (mMediaDevice != null) { + int profileIndex = mMediaDevice.getProfileIndex(profile); + Log.d(TAG, "profileIndex: " + profileIndex); + + if(codecStatus.equals(mMediaDevice.mProfileCodecStatus[profileIndex])) { + Log.w(TAG, "onCodecConfigChange: Codec already updated for the device and profile"); + return; + } + + mMediaDevice.mProfileCodecStatus[profileIndex] = codecStatus; + prevCodecStatus = mMediaDevice.mCodecStatus; + + /* Check the codec status for alternate Media profile for this device */ + if(mMediaDevice.mProfileCodecStatus[(profileIndex+1)%2] != null) { + mMediaDevice.mCodecStatus = convergeCodecConfig(mMediaDevice); + } else { + mMediaDevice.mCodecStatus = codecStatus; + } + + Log.w(TAG, "BroadCasting codecstatus " + mMediaDevice.mCodecStatus + + " for device: " + device); + broadcastCodecStatus(device, mMediaDevice.mCodecStatus); + } + + if(prevCodecStatus != null && mMediaDevice != null) { + if (prevCodecStatus.getCodecConfig().equals(mMediaDevice.mCodecStatus.getCodecConfig())) { + Log.d(TAG, "Previous and current codec config are same. Return"); + return; + } + } + + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null && (!mActiveDeviceManager.isStableState(ApmConst.AudioFeatures.MEDIA_AUDIO))) { + Log.d(TAG, "SHO under progress. MM Audio will be updated after SHO completes"); + return; + } + + if(device.equals(mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO)) && updateAudio) { + VolumeManager mVolumeManager = VolumeManager.get(); + int currentVolume = mVolumeManager.getActiveVolume(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (profile == ApmConst.AudioProfiles.BROADCAST_LE) + currentVolume = 15; + if (mAudioManager != null) { + BluetoothDevice groupDevice = device; + if(profile == ApmConst.AudioProfiles.BAP_MEDIA) { + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + groupDevice = mStreamAudioService.getDeviceGroup(device); + } + Log.d(TAG, "onCodecConfigChange Calling handleBluetoothA2dpActiveDeviceChange"); + mAudioManager.handleBluetoothA2dpActiveDeviceChange(groupDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, currentVolume); + } + } + } + + private void broadcastConnStateChange(BluetoothDevice device, int prevState, int newState) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if (mA2dpService != null) { + Log.d(TAG, "Broadcast Conn State Change: " + prevState + "->" + newState + " for device " + device); + Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + private void broadcastStreamState(BluetoothDevice device, int prevStatus, int streamStatus) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if (mA2dpService != null) { + Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevStatus); + intent.putExtra(BluetoothProfile.EXTRA_STATE, streamStatus); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + private void broadcastCodecStatus (BluetoothDevice device, BluetoothCodecStatus mCodecStatus) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if (mA2dpService != null) { + Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); + intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, mCodecStatus); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + public boolean isValidCodec (String mCodec) { + return supported_codec.contains(mCodec); + } + + public AudioManager getAudioManager() { + return mAudioManager; + } + + class MediaDevice { + BluetoothDevice mDevice; + int[] profileConnStatus = new int[2]; + int deviceConnStatus; + int streamStatus; + private BluetoothCodecStatus mCodecStatus; + private BluetoothCodecStatus[] mProfileCodecStatus = new BluetoothCodecStatus[2]; + + public static final int A2DP_STREAM = 0; + public static final int LE_STREAM = 1; + + MediaDevice(BluetoothDevice device, int profile, int state) { + profileConnStatus[A2DP_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + profileConnStatus[LE_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + mDevice = device; + if((profile & ApmConst.AudioProfiles.A2DP) != ApmConst.AudioProfiles.NONE) { + profileConnStatus[A2DP_STREAM] = state; + } + if((profile & (ApmConst.AudioProfiles.TMAP_MEDIA | ApmConst.AudioProfiles.BAP_MEDIA)) != + ApmConst.AudioProfiles.NONE) { + profileConnStatus[LE_STREAM] = state; + } + deviceConnStatus = state; + streamStatus = BluetoothA2dp.STATE_NOT_PLAYING; + } + + MediaDevice(BluetoothDevice device, int profile) { + this(device, profile, BluetoothProfile.STATE_DISCONNECTED); + } + + public int getProfileIndex(int profile) { + if(profile == ApmConst.AudioProfiles.A2DP) + return A2DP_STREAM; + else + return LE_STREAM; + } + } + + private class LeCodecConfig extends BroadcastReceiver { + /*am broadcast -a qti.intent.bluetooth.action.UPDATE_CODEC_CONFIG --es + qti.bluetooth.extra.CODEC_ID "LC3" --es qti.bluetooth.extra.CODEC_CONFIG ""*/ + + ArrayList supported_codec_config = new ArrayList( List.of( + /* config ID Sampling Freq Octets/Frame */ + "8_1", /* 8 26 */ + "8_2", /* 8 30 */ + "16_1", /* 16 30 */ + "16_2", /* 16 40 */ + "24_1", /* 24 45 */ + "24_2", /* 24 60 */ + "32_1", /* 32 60 */ + "32_2", /* 32 80 */ + "441_1",/* 44.1 98 */ + "441_2",/* 44.1 130 */ + "48_1", /* 48 75 */ + "48_2", /* 48 100 */ + "48_3", /* 48 90 */ + "48_4", /* 48 120 */ + "48_5", /* 48 117 */ + "48_6", /* 48 155 */ + "GCP_TX", + "GCP_TX_RX" + )); + + Map channel_mode = Map.of( + "NONE", 0, + "MONO", 1, + "STEREO", 2 + ); + + @Override + public void onReceive(Context context, Intent intent) { + if (!ACTION_UPDATE_CODEC_CONFIG.equals(intent.getAction())) { + return; + } + String mCodecId = intent.getStringExtra(CODEC_ID); + if(mCodecId == null || !isValidCodec(mCodecId)) { + Log.w(TAG, "Invalid Codec " + mCodecId); + return; + } + String mCodecConfig = intent.getStringExtra(CODEC_CONFIG); + if(mCodecConfig == null || !isValidCodecConfig(mCodecConfig)) { + Log.w(TAG, "Invalid Codec Config " + mCodecConfig); + return; + } + + int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE; + String chMode = intent.getStringExtra(CHANNEL_MODE); + if(chMode != null && channel_mode.containsKey(chMode)) { + mChannelMode = channel_mode.get(chMode); + } + + ActiveDeviceManagerService mActiveDeviceManager + = ActiveDeviceManagerService.get(); + int profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + if(profile == ApmConst.AudioProfiles.BROADCAST_LE) { + /*Update Broadcast module here*/ + mBapBroadcastManager.setCodecPreference(mCodecConfig, mChannelMode); + } else if (profile == ApmConst.AudioProfiles.BAP_MEDIA) { + BluetoothDevice device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.setCodecConfig(device, mCodecConfig, mChannelMode); + } + Log.i(TAG, "Codec Config Request: Codec Name: " + mCodecId + " Config ID: " + + mCodecConfig + " mChannelMode: " + mChannelMode + " for profile: " + profile); + } + + boolean isValidCodecConfig (String mCodecConfig) { + return supported_codec_config.contains(mCodecConfig); + } + } + + private class QosConfigReceiver extends BroadcastReceiver { + /*am broadcast -a qti.intent.bluetooth.action.UPDATE_QOS_CONFIG --es + qti.bluetooth.extra.CODEC_ID "LC3" --es qti.bluetooth.extra.QOS_CONFIG ""*/ + boolean enable = false; + + ArrayList supported_Qos_config = new ArrayList( List.of( + "8_1_1", + "8_2_1", + "16_1_1", + "16_2_1", + "24_1_1", + "24_2_1", + "32_1_1", + "32_2_1", + "441_1_1", + "441_2_1", + "48_1_1", + "48_2_1", + "48_3_1", + "48_4_1", + "48_5_1", + "48_6_1", + + "8_1_2", + "8_2_2", + "16_1_2", + "16_2_2", + "24_1_2", + "24_2_2", + "32_1_2", + "32_2_2", + "441_1_2", + "441_2_2", + "48_1_2", + "48_2_2", + "48_3_2", + "48_4_2", + "48_5_2", + "48_6_2" + )); + + @Override + public void onReceive(Context context, Intent intent) { + if(!enable) + return; + if (!ACTION_UPDATE_QOS_CONFIG.equals(intent.getAction())) { + return; + } + + String mCodecId = intent.getStringExtra(CODEC_ID); + if(mCodecId == null || !isValidCodec(mCodecId)) { + Log.w(TAG, "Invalid Codec " + mCodecId); + return; + } + String mQosConfig = intent.getStringExtra(QOS_CONFIG); + if(mQosConfig == null || !isValidQosConfig(mQosConfig)) { + Log.w(TAG, "Invalid QosConfig " + mQosConfig); + return; + } + + ActiveDeviceManagerService mActiveDeviceManager + = ActiveDeviceManagerService.get(); + int profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + if(profile == ApmConst.AudioProfiles.BROADCAST_LE) { + /*Update Broadcast module here*/ + } else if (profile == ApmConst.AudioProfiles.BAP_MEDIA) { + /*Update ACM here*/ + } + Log.i(TAG, "New Qos Config ID: " + mQosConfig + " for profile: " + profile); + } + + boolean isValidQosConfig(String mQosConfig) { + return supported_Qos_config.contains(mQosConfig); + } + } + + class BapBroadcastManager { + void setCodecPreference(String codecConfig, int channelMode) { + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if(mBroadcastService != null) { + mBroadcastService.setCodecPreference(codecConfig, channelMode); + } + } + + BluetoothCodecStatus getCodecStatus() { + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if(mBroadcastService != null) { + return mBroadcastService.getCodecStatus(); + } + return null; + } + + boolean isBapBroadcastActive() { + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if(mBroadcastService != null) { + return mBroadcastService.isBroadcastActive(); + } + return false; + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java new file mode 100644 index 0000000000000000000000000000000000000000..1f197cbf2a05658ebb9b7d8c477218d04ad1582f --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java @@ -0,0 +1,211 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import android.os.Binder; +import android.os.HandlerThread; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemProperties; +import android.util.Log; +import com.android.internal.util.ArrayUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.AudioAttributes; +import android.media.AudioPlaybackConfiguration; +import android.media.MediaDescription; +import android.media.MediaMetadata; +import android.media.session.MediaSession; +import android.media.session.MediaSession.QueueItem; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; +import android.util.Log; +import android.util.StatsLog; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; + +import com.android.bluetooth.avrcp.MediaController; +import com.android.bluetooth.apm.StreamAudioService; +import com.android.bluetooth.mcp.McpService; + +public class MediaControlManager { + private static final boolean DBG = true; + private static final String TAG = "APM: MediaControlManager"; + + static MediaControlManager mMediaControlManager = null; + + PlaybackCallback mPlaybackCallbackCb; + //MediaControlCallback mMediaControlCallbackCb; + BroadcastReceiver mMediaControlReceiver; + private static Context mContext; + //private AudioManager mAudioManager; + private Handler mHandler; + private McpService mMcpService; + public static final String MusicPlayerControlServiceName = "com.android.bluetooth.mcp.McpService"; + public static final int MUSIC_PLAYER_CONTROL = McpService.MUSIC_PLAYER_CONTROL; + private MediaControlManager () { + mPlaybackCallbackCb = new PlaybackCallback(); + //mMediaControlCallbackCb = new MediaControlCallback(); + mMediaControlReceiver = new MediaControlReceiver(); + } + + public static MediaControlManager get() { + if(mMediaControlManager == null) { + mMediaControlManager = new MediaControlManager(); + } + Log.v(TAG, "get"); + return mMediaControlManager; + } + + public static void make(Context context) { + if(mMediaControlManager == null) { + mMediaControlManager = new MediaControlManager(); + mMediaControlManager.init(context); + MediaControlManagerIntf.init(mMediaControlManager); + Log.v(TAG, "init, New mMediaControlManager instance"); + } + } + + public void init(Context context) { + mContext = context; + + + + /*mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + HandlerThread thread = new HandlerThread("MediaControlThread"); + Looper looper = thread.getLooper(); + mHandler = new Handler(looper); + mAudioManager.registerAudioPlaybackCallback(mPlaybackCallbackCb, + mHandler);*/ + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + mContext.registerReceiver(mMediaControlReceiver, filter); + Log.v(TAG, "init done"); + } + + private void updateCurrentPlayer (int playerId, int browseId) { + } + + void handlePassthroughCmd(int op, int state) { + } + + private class PlaybackCallback extends AudioManager.AudioPlaybackCallback { + @Override + public void onPlaybackConfigChanged(List configs) { + super.onPlaybackConfigChanged(configs); + + /*Update Playback config*/ + } + } + + + public void onMetadataChanged(MediaMetadata metadata) { + /*Update Metadata change*/ + Log.v(TAG, "onMetadataChanged"); + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updateMetaData(metadata); + } + } + + public synchronized void onPlaybackStateChanged(PlaybackState state) { + /*Update Playback State*/ + Log.v(TAG, "onPlaybackStateChanged"); + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updatePlaybackState(state); + } + + } + + public synchronized void onPackageChanged(String packageName) { + Log.v(TAG, "onPackageChanged"); + mMcpService = McpService.getMcpService(); + boolean removed = false; + if (packageName == null) + removed = true; + if (mMcpService != null) { + mMcpService.updatePlayerName(packageName, removed); + } + } + public void onSessionDestroyed(String packageName) { + Log.v(TAG, "onSessionDestroyed"); + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updatePlayerName(packageName, true); + } + } + + public void onQueueChanged(List queue) { + + } + + private class MediaControlReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String packageName = null; + String action = intent.getAction(); + boolean removed = false; + Log.v(TAG, "action " + action); + if(action == null) + return; + + switch(action) { + case Intent.ACTION_PACKAGE_REMOVED: + packageName = intent.getData().getSchemeSpecificPart(); + /*handle package removed*/ + removed = true; + break; + case Intent.ACTION_PACKAGE_ADDED: + packageName = intent.getData().getSchemeSpecificPart(); + /*handle package added*/ + break; + case Intent.ACTION_PACKAGE_CHANGED: + packageName = intent.getData().getSchemeSpecificPart(); + /*handle package changed*/ + break; + default : + break; + } + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updatePlayerName(packageName, removed); + } + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java new file mode 100644 index 0000000000000000000000000000000000000000..fc42703773d236d7320d5484368cab49cc4df539 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java @@ -0,0 +1,382 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import static com.android.bluetooth.Utils.enforceBluetoothPermission; +import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; + +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.IBluetoothVcp; + +import android.os.Binder; +import android.os.HandlerThread; +import android.os.Handler; +import android.os.Message; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.acm.AcmService; + +public class StreamAudioService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = "APM: StreamAudioService:"; + public static final int LE_AUDIO_UNICAST = 26; + + public static final String CoordinatedAudioServiceName = "com.android.bluetooth.acm.AcmService"; + public static final int COORDINATED_AUDIO_UNICAST = AcmService.ACM_AUDIO_UNICAST; + + private static StreamAudioService sStreamAudioService; + private ActiveDeviceManagerService mActiveDeviceManager; + private MediaAudio mMediaAudio; + private VolumeManager mVolumeManager; + private final Object mVolumeManagerLock = new Object(); + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + private static final int BAP = 0x01; + private static final int GCP = 0x02; + private static final int WMCP = 0x04; + private static final int VMCP = 0x08; + private static final int BAP_CALL = 0x10; + + private static final int MEDIA_CONTEXT = 1; + private static final int VOICE_CONTEXT = 2; + + @Override + protected boolean start() { + if(sStreamAudioService != null) { + Log.i(TAG, "StreamAudioService already started"); + return true; + } + Log.i(TAG, "start()"); + + ApmConst.setLeAudioEnabled(true); + ApmConstIntf.init(); + + setStreamAudioService(this); + + mActiveDeviceManager = ActiveDeviceManagerService.get(this); + mMediaAudio = MediaAudio.init(this); + + DeviceProfileMap dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + dpm.init(this); + CallAudio mCallAudio = CallAudio.init(this); + synchronized (mVolumeManagerLock) { + mVolumeManager = VolumeManager.init(this); + } + + Log.i(TAG, "start() complete"); + return true; + } + + @Override + protected boolean stop() { + Log.w(TAG, "stop() called"); + if (sStreamAudioService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + + if (mActiveDeviceManager != null) { + mActiveDeviceManager.disable(); + mActiveDeviceManager.cleanup(); + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + dMap.cleanup(); + return true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + synchronized (mVolumeManagerLock) { + mVolumeManager.cleanup(); + mVolumeManager = null; + } + setStreamAudioService(null); + } + + public boolean connectLeStream(BluetoothDevice device, int profile) { + AcmService mAcmService = AcmService.getAcmService(); + int mContext = getContext(profile); + + if(mContext == 0) { + Log.e(TAG, "No valid context for profiles passed"); + return false; + } + return mAcmService.connect(device, mContext, getAcmProfileID(profile), MEDIA_CONTEXT); + //return mAcmService.connect(device, VOICE_CONTEXT, BAP_CALL, VOICE_CONTEXT); + //return mAcmService.connect(device, MEDIA_CONTEXT, BAP|WMCP, MEDIA_CONTEXT); + } + + public boolean disconnectLeStream(BluetoothDevice device, boolean callAudio, boolean mediaAudio) { + AcmService mAcmService = AcmService.getAcmService(); + if(callAudio && mediaAudio) + return mAcmService.disconnect(device, VOICE_CONTEXT | MEDIA_CONTEXT); + //return mAcmService.disconnect(device, VOICE_CONTEXT); + //return mAcmService.disconnect(device, MEDIA_CONTEXT); + else if(mediaAudio) + return mAcmService.disconnect(device, MEDIA_CONTEXT); + else if(callAudio) + return mAcmService.disconnect(device, VOICE_CONTEXT); + + return false; + } + + public boolean startStream(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.StartStream(device, VOICE_CONTEXT); + } + + public boolean stopStream(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.StopStream(device, VOICE_CONTEXT); + } + + public int setActiveDevice(BluetoothDevice device, int profile, boolean playReq) { + AcmService mAcmService = AcmService.getAcmService(); + if (mAcmService == null && device == null) { + Log.w(TAG, ": device is null, fake success."); + return mActiveDeviceManager.SHO_SUCCESS; + } + + if(ApmConst.AudioProfiles.BAP_MEDIA == profile) { + return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, BAP, playReq); + } else if(ApmConst.AudioProfiles.BAP_GCP == profile){ + return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, GCP, playReq); + } else if(ApmConst.AudioProfiles.BAP_RECORDING == profile){ + return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, WMCP, playReq); + } else { + return mAcmService.setActiveDevice(device, VOICE_CONTEXT, BAP_CALL, playReq); + //return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, BAP, playReq); + } + } + + public void setCodecConfig(BluetoothDevice device, String codecID, int channelMode) { + AcmService mAcmService = AcmService.getAcmService(); + mAcmService.ChangeCodecConfigPreference(device, codecID); + } + + public BluetoothDevice getDeviceGroup(BluetoothDevice device){ + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.getGroup(device); + } + + public void onConnectionStateChange(BluetoothDevice device, int state, int audioType, boolean primeDevice) { + MediaAudio mMediaAudio = MediaAudio.get(); + CallAudio mCallAudio = CallAudio.get(); + int profile = ApmConst.AudioFeatures.MAX_AUDIO_FEATURES; + if(audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + mCallAudio.onConnStateChange(device, state, ApmConst.AudioProfiles.BAP_CALL); + } else if(audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + boolean isCsipDevice = (device != null) && + getDeviceGroup(device).getAddress().contains(ApmConst.groupAddress); + if(isCsipDevice) + mMediaAudio.onConnStateChange(device, state, ApmConst.AudioProfiles.BAP_MEDIA, primeDevice); + else + mMediaAudio.onConnStateChange(device, state, ApmConst.AudioProfiles.BAP_MEDIA); + } + } + + public void onStreamStateChange(BluetoothDevice device, int state, int audioType) { + MediaAudio mMediaAudio = MediaAudio.get(); + CallAudio mCallAudio = CallAudio.get(); + if(audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) + mMediaAudio.onStreamStateChange(device, state); + else if(audioType == ApmConst.AudioFeatures.CALL_AUDIO) + mCallAudio.onAudioStateChange(device, state); + } + + public void onActiveDeviceChange(BluetoothDevice device, int audioType) { + if (mActiveDeviceManager != null) + mActiveDeviceManager.onActiveDeviceChange(device, audioType); + } + + public void onMediaCodecConfigChange(BluetoothDevice device, BluetoothCodecStatus codecStatus, int audioType) { + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.onCodecConfigChange(device, codecStatus, ApmConst.AudioProfiles.BAP_MEDIA); + } + + public void onMediaCodecConfigChange(BluetoothDevice device, BluetoothCodecStatus codecStatus, int audioType, boolean updateAudio) { + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.onCodecConfigChange(device, codecStatus, ApmConst.AudioProfiles.BAP_MEDIA, updateAudio); + } + + public void setCallAudioParam(String param) { + CallAudio mCallAudio = CallAudio.get(); + mCallAudio.setAudioParam(param); + } + + public void setCallAudioOn(boolean on) { + CallAudio mCallAudio = CallAudio.get(); + mCallAudio.setBluetoothScoOn(on); + } + + public int getVcpConnState(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return BluetoothProfile.STATE_DISCONNECTED; + return mVolumeManager.getConnectionState(device); + } + } + + public int getConnectionMode(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return BluetoothProfile.STATE_DISCONNECTED; + return mVolumeManager.getConnectionMode(device); + } + } + + public void setAbsoluteVolume(BluetoothDevice device, int volume) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager != null) + mVolumeManager.updateBroadcastVolume(device, volume); + } + } + + public int getAbsoluteVolume(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return 7; + return mVolumeManager.getBassVolume(device); + } + } + + public void setMute(BluetoothDevice device, boolean muteStatus) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager != null) + mVolumeManager.setMute(device, muteStatus); + } + } + + public boolean isMute(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return false; + return mVolumeManager.getMuteStatus(device); + } + } + + private int getContext(int profileID) { + int context = 0; + if((DeviceProfileMap.getLeMediaProfiles() & profileID) > 0) { + context = (context|MEDIA_CONTEXT); + } + + if((DeviceProfileMap.getLeCallProfiles() & profileID) > 0) { + context = (context|VOICE_CONTEXT); + } + return context; + } + + private int getAcmProfileID (int ProfileID) { + int AcmProfileID = 0; + if((ApmConst.AudioProfiles.BAP_MEDIA & ProfileID) == ApmConst.AudioProfiles.BAP_MEDIA) + AcmProfileID = BAP; + if((ApmConst.AudioProfiles.BAP_CALL & ProfileID) == ApmConst.AudioProfiles.BAP_CALL) + AcmProfileID = AcmProfileID | BAP_CALL; + if((ApmConst.AudioProfiles.BAP_GCP & ProfileID) == ApmConst.AudioProfiles.BAP_GCP) + AcmProfileID = AcmProfileID | GCP; + if((ApmConst.AudioProfiles.BAP_RECORDING & ProfileID) == ApmConst.AudioProfiles.BAP_RECORDING) + AcmProfileID = AcmProfileID | WMCP; + return AcmProfileID; + } + + @Override + protected IProfileServiceBinder initBinder() { + return new LeAudioUnicastBinder(this); + } + + private static class LeAudioUnicastBinder extends IBluetoothVcp.Stub implements IProfileServiceBinder { + + StreamAudioService mService; + LeAudioUnicastBinder(StreamAudioService service) { + mService = service; + } + + @Override + public void cleanup() { + } + + @Override + public int getConnectionState(BluetoothDevice device) { + if(mService == null) + return BluetoothProfile.STATE_DISCONNECTED; + return mService.getVcpConnState(device); + } + + @Override + public int getConnectionMode(BluetoothDevice device) { + if(mService != null) { + return mService.getConnectionMode(device); + } + return 0; + } + + @Override + public void setAbsoluteVolume(BluetoothDevice device, int volume) { + if(mService != null) { + mService.setAbsoluteVolume(device, volume); + } + } + + @Override + public int getAbsoluteVolume(BluetoothDevice device) { + if(mService == null) + return 7; + return mService.getAbsoluteVolume(device); + } + + @Override + public void setMute (BluetoothDevice device, boolean enableMute) { + if(mService != null) { + mService.setMute(device, enableMute); + } + } + + @Override + public boolean isMute(BluetoothDevice device) { + if(mService != null) { + return mService.isMute(device); + } + return false; + } + } + + public static StreamAudioService getStreamAudioService() { + return sStreamAudioService; + } + + private static synchronized void setStreamAudioService(StreamAudioService instance) { + if (DBG) { + Log.d(TAG, "setStreamAudioService(): set to: " + instance); + } + sStreamAudioService = instance; + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java new file mode 100644 index 0000000000000000000000000000000000000000..a57195778cce4018af93b4d8fa2be26463c71fa8 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java @@ -0,0 +1,779 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.apm; + +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothHeadset; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.a2dp.A2dpService; +import com.android.bluetooth.avrcp.Avrcp_ext; +import com.android.bluetooth.acm.AcmService; +import com.android.bluetooth.bc.BCService; +import com.android.bluetooth.hfp.HeadsetService; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.util.Log; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.List; +import java.util.Map; + +public class VolumeManager { + public static final String TAG = "APM: VolumeManager"; + private static VolumeManager mVolumeManager = null; + private DeviceVolume mMedia; + private DeviceVolume mCall; + private DeviceVolume mBroadcast; + private DeviceProfileMap dpm; + private MediaAudio mMediaAudio; + private CallAudio mCallAudio; + private static Context mContext; + BroadcastReceiver mVolumeManagerReceiver; + Map AbsVolumeSupport; + + public static final String CALL_VOLUME_MAP = "bluetooth_call_volume_map"; + public static final String MEDIA_VOLUME_MAP = "bluetooth_media_volume_map"; + public static final String BROADCAST_VOLUME_MAP = "bluetooth_broadcast_volume_map"; + public final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + public final String ACTION_POWER_OFF = "android.intent.action.QUICKBOOT_POWEROFF"; + + private VolumeManager() { + mCall = new DeviceVolume(mContext, CALL_VOLUME_MAP); + mMedia = new DeviceVolume(mContext, MEDIA_VOLUME_MAP); + mBroadcast = new DeviceVolume(mContext, BROADCAST_VOLUME_MAP); + + dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + mMediaAudio = MediaAudio.get(); + mCallAudio = CallAudio.get(); + + AbsVolumeSupport = new ConcurrentHashMap(); + + mVolumeManagerReceiver = new VolumeManagerReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + filter.addAction(ACTION_SHUTDOWN); + filter.addAction(ACTION_POWER_OFF); + filter.addAction(BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO); + mContext.registerReceiver(mVolumeManagerReceiver, filter); + } + + public static VolumeManager init (Context context) { + mContext = context; + + if(mVolumeManager == null) { + mVolumeManager = new VolumeManager(); + VolumeManagerIntf.init(mVolumeManager); + } + + return mVolumeManager; + } + + public void cleanup() { + Log.i(TAG, "cleanup"); + handleDeviceShutdown(); + synchronized (mVolumeManager) { + mCall = null; + mMedia = null; + mBroadcast = null; + mContext.unregisterReceiver(mVolumeManagerReceiver); + mVolumeManagerReceiver = null; + AbsVolumeSupport.clear(); + AbsVolumeSupport = null; + mVolumeManager = null; + } + } + + public static VolumeManager get() { + return mVolumeManager; + } + + private DeviceVolume VolumeType(int mAudioType) { + if(ApmConst.AudioFeatures.CALL_AUDIO == mAudioType) { + return mCall; + } else if(ApmConst.AudioFeatures.MEDIA_AUDIO == mAudioType) { + return mMedia; + } else if(ApmConst.AudioFeatures.BROADCAST_AUDIO == mAudioType) { + return mBroadcast; + } + return null; + } + + public int getConnectionMode(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService == null) { + return -1; + } + return mAcmService.getVcpConnMode(device); + } + + public void setMediaAbsoluteVolume (Integer volume) { + if(mMedia.mDevice == null) { + Log.e (TAG, "setMediaAbsoluteVolume: No Device Active for Media. Ignore"); + return; + } + mMedia.updateVolume(volume); + + if(ApmConst.AudioProfiles.AVRCP == mMedia.mProfile) { + Avrcp_ext mAvrcp = Avrcp_ext.get(); + if(mAvrcp != null) { + Log.i (TAG, "setMediaAbsoluteVolume: Updating new volume to AVRCP: " + volume); + mAvrcp.setAbsoluteVolume(volume); + } + } else if(ApmConst.AudioProfiles.VCP == mMedia.mProfile) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService != null) { + Log.i (TAG, "setMediaAbsoluteVolume: Updating new volume to VCP: " + volume); + mMedia.updateVolume(volume); + mAcmService.setAbsoluteVolume(mMedia.mDevice, volume, ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + } + + public void updateMediaStreamVolume (Integer volume) { + if(mMedia.mDevice == null) { + Log.e (TAG, "updateMediaStreamVolume: No Device Active for Media. Ignore"); + return; + } + + if(mMedia.mSupportAbsoluteVolume) { + /* Ignore: Will update volume via API call */ + return; + } + mMedia.updateVolume(volume); + } + + public void updateBroadcastVolume (BluetoothDevice device, int volume) { + int callAudioState = mCallAudio.getAudioState(device); + boolean isCall = (callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTING || + callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED); + if (isCall) { + Log.e(TAG, "Call in progress, ignore volume change"); + return; + } + + mBroadcast.updateVolume(device, volume); + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice = mAcmService.getGroup(device); + mAcmService.setAbsoluteVolume(mGroupDevice, volume, ApmConst.AudioFeatures.BROADCAST_AUDIO); + mBroadcast.updateVolume(mGroupDevice, volume); + } + + public void setMute(BluetoothDevice device, boolean muteStatus) { + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice = mAcmService.getGroup(device); + mAcmService.setMute(mGroupDevice, muteStatus); + } + + public void restoreCallVolume (Integer volume) { + if(mCall.mDevice == null) { + Log.e (TAG, "restoreCallVolume: No Device Active for Call. Ignore"); + return; + } + + if(ApmConst.AudioProfiles.HFP == mCall.mProfile) { + // Ignore restoring call volume for HFP case + Log.w (TAG, "restoreCallVolume: Ignore restore call volume for HFP"); + } else if(ApmConst.AudioProfiles.VCP == mCall.mProfile) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService != null) { + Log.i (TAG, "restoreCallVolume: Updating new volume to VCP: " + volume); + mCall.updateVolume(volume); + mAcmService.setAbsoluteVolume(mCall.mDevice, volume, ApmConst.AudioFeatures.CALL_AUDIO); + } + // TODO: Restore call volume to MM-Audio also + } + } + + public void setCallVolume (Intent intent) { + if(mCall.mDevice == null) { + Log.e (TAG, "setCallVolume: No Device Active for Call. Ignore"); + return; + } + + int volume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); + if(ApmConst.AudioProfiles.HFP == mCall.mProfile) { + Log.i (TAG, "setCallVolume: Updating new volume to HFP: " + volume); + HeadsetService headsetService = HeadsetService.getHeadsetService(); + headsetService.setIntentScoVolume(intent); + } else if(ApmConst.AudioProfiles.VCP == mCall.mProfile) { + Log.i (TAG, "setCallVolume: mCall volume: " + mCall.mVolume + ", volume: " + volume); + // Avoid updating same call volume after remote volume change + if (volume == mCall.mVolume) { + Log.w (TAG, "setCallVolume: Ignore updating same call volume to remote"); + return; + } + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService != null) { + Log.i (TAG, "setCallVolume: Updating new volume to VCP: " + volume); + mCall.updateVolume(volume); + mAcmService.setAbsoluteVolume(mCall.mDevice, volume, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + } + + public int getConnectionState(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.getVcpConnState(device); + } + + public void onConnStateChange(BluetoothDevice device, Integer state, Integer profile) { + Log.d (TAG, "onConnStateChange: state: " + state + " Profile: " + profile); + if (device == null) { + Log.e (TAG, "onConnStateChange: device is null. Ignore"); + return; + } + + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice; + if(mAcmService != null) { + mGroupDevice = mAcmService.getGroup(device); + } else { + mGroupDevice = device; + } + + if (mGroupDevice.equals(mMedia.mDevice)) { + mMedia.mProfile = + dpm.getProfile(mGroupDevice, ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL); + } + if (mGroupDevice.equals(mCall.mDevice)) { + mCall.mProfile = + dpm.getProfile(mGroupDevice, ApmConst.AudioFeatures.CALL_VOLUME_CONTROL); + } + + if (state == BluetoothProfile.STATE_CONNECTED) { + int audioType = getActiveAudioType(device); + if (ApmConst.AudioFeatures.MEDIA_AUDIO == audioType && mMedia.mProfile == profile) { + Log.d (TAG, "onConnStateChange: Media is streaming or active, update media volume"); + setMediaAbsoluteVolume(mMedia.mVolume); + } else if (ApmConst.AudioFeatures.CALL_AUDIO == audioType && + mCall.mProfile == profile) { + Log.d (TAG, "onConnStateChange: Call is streaming, update call volume"); + restoreCallVolume(mCall.mVolume); + } else if (ApmConst.AudioFeatures.BROADCAST_AUDIO == audioType) { + Log.d (TAG, "onConnStateChange: Broadcast is streaming, update broadcast volume"); + updateBroadcastVolume(device, getBassVolume(device)); + } + } + } + + public void onVolumeChange(Integer volume, Integer audioType, Boolean showUI) { + int flag = showUI ? AudioManager.FLAG_SHOW_UI : 0; + if(audioType == ApmConst.AudioFeatures.CALL_AUDIO){ + mCall.updateVolume(volume); + mCallAudio.getAudioManager().setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, + volume, flag); + } else if(audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + mMedia.updateVolume(volume); + mMediaAudio.getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC, volume, + flag | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); + } + } + + public void onVolumeChange(BluetoothDevice device, Integer volume, Integer audioType) { + if ((VolumeType(audioType) == mCall && device.equals(mCall.mDevice)) || + (VolumeType(audioType) == mMedia && device.equals(mMedia.mDevice))) { + onVolumeChange(volume, audioType, true); + } else { + mBroadcast.updateVolume(device, volume); + } + } + + public void onMuteStatusChange(BluetoothDevice device, boolean isMute, int audioType) { + } + + + public void onActiveDeviceChange(BluetoothDevice device, int audioType) { + if(device == null) { + synchronized(mVolumeManager) { + if(VolumeType(audioType) != null) + VolumeType(audioType).reset(); + } + } else { + int mProfile = dpm.getProfile(device, audioType == ApmConst.AudioFeatures.CALL_AUDIO? + ApmConst.AudioFeatures.CALL_VOLUME_CONTROL:ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL); + DeviceVolume mDeviceVolume = VolumeType(audioType); + mDeviceVolume.updateDevice(device, mProfile); + Log.i(TAG, "ActiveDeviceChange: device: " + mDeviceVolume.mDevice + ". AudioType: " + audioType); + if(mDeviceVolume.equals(mMedia)) { + int mAbsVolSupportProfiles = AbsVolumeSupport.getOrDefault(device.getAddress(), 0); + boolean isAbsSupported = ((mProfile & mAbsVolSupportProfiles) != 0) ? true : false; + Log.i(TAG, "isAbsoluteVolumeSupport: " + isAbsSupported); + mDeviceVolume.mSupportAbsoluteVolume = isAbsSupported; + mMediaAudio.getAudioManager().avrcpSupportsAbsoluteVolume ( + device.getAddress(), isAbsSupported); + + Log.i(TAG, "ActiveDeviceChange: Profile: " + mProfile + ". New Volume: " + mDeviceVolume.mVolume); + if (!isBroadcastAudioSynced(device) || + (mMediaAudio.isA2dpPlaying(device) && mMediaAudio.getAudioManager().isMusicActive())) { + setMediaAbsoluteVolume(mDeviceVolume.mVolume); + } + } + } + } + + public void updateStreamState(BluetoothDevice device, Integer streamState, Integer audioType) { + boolean isMusicActive = false; + if (device == null) { + Log.e (TAG, "updateStreamState: device is null. Ignore"); + return; + } + if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO && + streamState == BluetoothA2dp.STATE_PLAYING) { + isMusicActive = mMediaAudio.getAudioManager().isMusicActive(); + } + Log.d(TAG, "updateStreamState, device: " + device + " type: " + audioType + + " streamState: " + streamState + " isMusicActive: " + isMusicActive); + + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice; + if(mAcmService != null) { + mGroupDevice = mAcmService.getGroup(device); + } else { + mGroupDevice = device; + } + + if ((audioType == ApmConst.AudioFeatures.MEDIA_AUDIO && + streamState == BluetoothA2dp.STATE_NOT_PLAYING) || + (audioType == ApmConst.AudioFeatures.CALL_AUDIO && + streamState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) { + if (isBroadcastAudioSynced(device)) { + handleBroadcastAudioSynced(device); + } + } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO && + streamState == BluetoothA2dp.STATE_PLAYING && isMusicActive) { + if (mGroupDevice.equals(mMedia.mDevice)) { + Log.d(TAG, "Restore volume for A2dp streaming"); + setMediaAbsoluteVolume(mMedia.mVolume); + } + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO && + streamState == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + if (mGroupDevice.equals(mCall.mDevice)) { + Log.d(TAG, "Restore volume for call"); + restoreCallVolume(mCall.mVolume); + } + } + } + + public int getActiveAudioType(BluetoothDevice device) { + int callAudioState = mCallAudio.getAudioState(device); + boolean isCall = (callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTING || + callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED); + int audioType = -1; + + if (device == null) { + Log.e (TAG, "getActiveAudioType: device is null. Ignore"); + return audioType; + } + + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice; + if(mAcmService != null) { + mGroupDevice = mAcmService.getGroup(device); + } else { + mGroupDevice = device; + } + + if (mMediaAudio.isA2dpPlaying(device) && + mMediaAudio.getAudioManager().isMusicActive()) { + if (mGroupDevice.equals(mMedia.mDevice)) { + Log.d(TAG, "Active Media audio is streaming"); + audioType = ApmConst.AudioFeatures.MEDIA_AUDIO; + } + } else if (isCall) { + if (mGroupDevice.equals(mCall.mDevice)) { + Log.d(TAG, "Active Call audio is streaming"); + audioType = ApmConst.AudioFeatures.CALL_AUDIO; + } + } else if (isBroadcastAudioSynced(device)) { + Log.d(TAG, "Broadcast audio is streaming"); + audioType = ApmConst.AudioFeatures.BROADCAST_AUDIO; + } else { + Log.d(TAG, "None of audio is streaming"); + ActiveDeviceManagerService activeDeviceManager = + ActiveDeviceManagerService.get(mContext); + BluetoothDevice activeDevice = + activeDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (mGroupDevice.equals(mMedia.mDevice) && mGroupDevice.equals(activeDevice)) { + Log.d(TAG, "Peer is Media active, set for media type by default"); + audioType = ApmConst.AudioFeatures.MEDIA_AUDIO; + } else { + Log.d(TAG, "Inactive peer, unknow audio type"); + } + } + + Log.d(TAG, "getActiveAudioType: ret " + audioType); + return audioType; + } + + /*Should be called by AVRCP and VCP after every connection*/ + public void setAbsoluteVolumeSupport(BluetoothDevice device, Boolean isSupported, + Integer initVol, Integer profile) { + setAbsoluteVolumeSupport(device, isSupported, profile); + } + + public void setAbsoluteVolumeSupport(BluetoothDevice device, Boolean isSupported, + Integer profile) { + Log.i(TAG, "setAbsoluteVolumeSupport device " + device + " profile " + profile + + " isSupported " + isSupported); + if(device == null) + return; + + int mProfile = dpm.getProfile(device, ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL); + int mAbsVolSupportProfiles = AbsVolumeSupport.getOrDefault(device.getAddress(), 0); + if (isSupported) { + mAbsVolSupportProfiles = mAbsVolSupportProfiles | profile; + } else { + mAbsVolSupportProfiles = mAbsVolSupportProfiles & ~profile; + } + + if(device.equals(mMedia.mDevice)) { + boolean isAbsSupported = ((mProfile & mAbsVolSupportProfiles) != 0) ? true : false; + Log.i(TAG, "Update abs volume support: " + isAbsSupported); + mMedia.mSupportAbsoluteVolume = isAbsSupported; + mMediaAudio.getAudioManager().avrcpSupportsAbsoluteVolume ( + device.getAddress(), isAbsSupported); + + if(mMedia.mProfile == ApmConst.AudioProfiles.NONE) { + mMedia.mProfile = mProfile; + Log.i(TAG, "setAbsoluteVolumeSupport: Profile: " + mMedia.mProfile); + } + } + AbsVolumeSupport.put(device.getAddress(), mAbsVolSupportProfiles); + } + + public void saveVolume(Integer audioType) { + VolumeType(audioType).saveVolume(); + } + + public int getSavedVolume(BluetoothDevice device, Integer audioType) { + return VolumeType(audioType).getSavedVolume(device); + } + + public int getActiveVolume(Integer audioType) { + return VolumeType(audioType).mVolume; + } + + public int getBassVolume(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice = mAcmService.getGroup(device); + int volume = mBroadcast.getVolume(mGroupDevice); + Log.i(TAG, "getBassVolume: " + device + " volume: " + volume); + return volume; + } + + public boolean getMuteStatus(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService == null) { + return false; + } + return mAcmService.isVcpMute(device); + } + + boolean isBroadcastAudioSynced(BluetoothDevice device) { + BCService mBCService = BCService.getBCService(); + if (mBCService == null || device == null) return false; + List srcInfos = + mBCService.getAllBroadcastSourceInformation(device); + if (srcInfos == null || srcInfos.size() == 0) { + Log.e(TAG, "source Infos not available"); + return false; + } + + for (int i=0; i mBassVolMap; + + Context mContext; + private String mAudioTypeStr; + public static final int SAFE_VOL = 7; + public String mVolumeMap; + + DeviceVolume(Context context, String map) { + this.reset(); + mContext = context; + mVolumeMap = map; + mSupportAbsoluteVolume = false; + + if(map == "bluetooth_call_volume_map") { + mAudioTypeStr = "Call"; + } + else if(map == "bluetooth_media_volume_map") { + mAudioTypeStr = "Media"; + } + else { + mAudioTypeStr = "Broadcast"; + mBassVolMap = new ConcurrentHashMap(); + } + + Map allKeys = getVolumeMap().getAll(); + SharedPreferences.Editor pref = getVolumeMap().edit(); + for (Map.Entry entry : allKeys.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + BluetoothDevice d = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(key); + + if (value instanceof Integer && d.getBondState() == BluetoothDevice.BOND_BONDED) { + if (mAudioTypeStr.equals("Broadcast")) { + mBassVolMap.put(key, (Integer) value); + Log.w(TAG, "address " + key + " from the broadcast volume map volume :" + value); + } + } else { + Log.w(TAG, "Removing " + key + " from the " + mAudioTypeStr + " volume map"); + pref.remove(key); + } + } + pref.apply(); + } + + void updateDevice (BluetoothDevice device, int profile) { + mDevice = device; + mProfile = profile; + + mVolume = getSavedVolume(device); + Log.i (TAG, "New " + mAudioTypeStr + " device: " + mDevice + " Vol: " + mVolume); + } + + int getSavedVolume (BluetoothDevice device) { + int mSavedVolume; + SharedPreferences pref = getVolumeMap(); + mSavedVolume = pref.getInt(device.getAddress(), SAFE_VOL); + return mSavedVolume; + } + + void updateVolume (int volume) { + mVolume = volume; + } + + void updateVolume (BluetoothDevice device, int volume) { + if(mAudioTypeStr.equals("Broadcast")) { + Log.i(TAG, "updateVolume, device " + device + " volume: " + volume); + mBassVolMap.put(device.getAddress(), volume); + } + } + int getVolume(BluetoothDevice device) { + if(device == null) { + Log.e (TAG, "Null Device passed"); + return 7; + } + if(mAudioTypeStr.equals("Broadcast")) { + if(mBassVolMap.containsKey(device.getAddress())) { + return mBassVolMap.getOrDefault(device.getAddress(), 7); + } else { + int mSavedVolume = getSavedVolume(device); + mBassVolMap.put(device.getAddress(), mSavedVolume); + Log.i(TAG, "get saved volume, device " + device + " volume: " + mSavedVolume); + return mSavedVolume; + } + } + return 7; + } + private SharedPreferences getVolumeMap() { + return mContext.getSharedPreferences(mVolumeMap, Context.MODE_PRIVATE); + } + + public void saveVolume() { + if(mAudioTypeStr.equals("Broadcast")) { + saveBroadcastVolume(); + return; + } + + if(mDevice == null) { + Log.e (TAG, "saveVolume: No Device Active for " + mAudioTypeStr + ". Ignore"); + return; + } + + SharedPreferences.Editor pref = getVolumeMap().edit(); + pref.putInt(mDevice.getAddress(), mVolume); + pref.apply(); + Log.i (TAG, "Saved " + mAudioTypeStr + " Volume: " + mVolume + " for device: " + mDevice); + } + + public void saveBroadcastVolume() { + SharedPreferences.Editor pref = getVolumeMap().edit(); + for(Map.Entry itr : mBassVolMap.entrySet()) { + pref.putInt(itr.getKey(), itr.getValue()); + } + pref.apply(); + } + + public void saveVolume(BluetoothDevice device) { + if(device == null) { + Log.e (TAG, "Null Device passed"); + return; + } + if(mAudioTypeStr.equals("Broadcast")) { + int mVol = mBassVolMap.getOrDefault(device.getAddress(), 7); + SharedPreferences.Editor pref = getVolumeMap().edit(); + pref.putInt(device.getAddress(), mVol); + pref.apply(); + } + } + + void removeDevice(BluetoothDevice device) { + if(mAudioTypeStr.equals("Broadcast")) { + Log.i (TAG, "Remove device " + device + " from broadcast volume map "); + mBassVolMap.remove(device.getAddress()); + } + SharedPreferences.Editor pref = getVolumeMap().edit(); + pref.remove(device.getAddress()); + pref.apply(); + } + + void reset () { + Log.i (TAG, "Reset " + mAudioTypeStr + " Device: " + mDevice); + mDevice = null; + mVolume = SAFE_VOL; + mProfile = ApmConst.AudioProfiles.NONE; + } + } + + private class VolumeManagerReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if(action == null) + return; + + switch(action) { + case AudioManager.VOLUME_CHANGED_ACTION: + int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); + if(streamType == AudioManager.STREAM_BLUETOOTH_SCO) { + setCallVolume(intent); + } else { + updateMediaStreamVolume(volumeValue); + } + break; + + case BluetoothDevice.ACTION_BOND_STATE_CHANGED: + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if(device == null) + return; + + if(state == BluetoothDevice.BOND_NONE) { + handleDeviceUnbond(device); + } + break; + + case BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO: + BleBroadcastSourceInfo sourceInfo = intent.getParcelableExtra( + BleBroadcastSourceInfo.EXTRA_SOURCE_INFO); + device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + if (device == null || sourceInfo == null) { + Log.w (TAG, "Bluetooth Device or Source info is null"); + break; + } + + if (sourceInfo.getAudioSyncState() == + BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { + handleBroadcastAudioSynced(device); + } + break; + + case ACTION_SHUTDOWN: + case ACTION_POWER_OFF: + handleDeviceShutdown(); + break; + } + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java new file mode 100644 index 0000000000000000000000000000000000000000..ebcbf81b541a79ee03421157f76918316f4aa71c --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java @@ -0,0 +1,1691 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.bc; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + + +import android.app.ActivityManager; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.IBluetoothSyncHelper; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; + +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.HandlerThread; +import android.util.Log; +import android.os.ParcelUuid; +import android.bluetooth.BluetoothUuid; +import java.util.ArrayList; +import android.os.ServiceManager; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.bluetooth.btservice.AdapterService; +//*_CSIP +//CSIP related imports +import com.android.bluetooth.groupclient.GroupService; +import android.bluetooth.BluetoothGroupCallback; +import android.bluetooth.DeviceGroup; +//_CSIP*/ + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.UUID; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Objects; +import java.util.NoSuchElementException; +import android.os.SystemProperties; + + +import com.android.internal.util.ArrayUtils; +/** @hide */ +public class BCService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = BCService.class.getSimpleName(); + + private static final ParcelUuid CAS_UUID = null;//ParcelUuid.fromString("000089FF-0000-1000-8000-00805F9B34FB"); + + public static final String BC_ID = "0000184F-0000-1000-8000-00805F9B34FB"; + public static final String BS_ID = "00001852-0000-1000-8000-00805F9B34FB"; + private static BCService sBCService; + private static final int MAX_BASS_CLIENT_STATE_MACHINES = 10; + private static final int MAX_BASS_CLIENT_CSET_MEMBERS = 10; + private final Map mStateMachines = + new HashMap<>(); + private HandlerThread mStateMachinesThread; + private final Map mSetManagers = + new HashMap<>(); + private HandlerThread mSetManagerThread; + + private AdapterService mAdapterService; + + private Map> mAppCallbackMap = + new HashMap>(); + + private BassUtils bassUtils = null; + public static final int INVALID_SYNC_HANDLE = -1; + public static final int INVALID_ADV_SID = -1; + public static final int INVALID_ADV_ADDRESS_TYPE = -1; + public static final int INVALID_ADV_INTERVAL = -1; + public static final int INVALID_BROADCAST_ID = -1; + private Map mActiveSourceMap; + + //*_CSIP + //CSET interfaces + private GroupService mSetCoordinator = GroupService.getGroupService(); + public int mCsipAppId = -1; + private int mQueuedOps = 0; + //_CSIP*/ + + /*Caching the PAresults from Broadcast source*/ + /*This is stored at service so that each device state machine can access + and use it as needed. Once the periodic sync in cancelled, this data will bre + removed to ensure stable data won't used*/ + /*broadcastSrcDevice, syncHandle*/ + private Map mSyncHandleMap; + /*syncHandle, parsed BaseData data*/ + private Map mSyncHandleVsBaseInfo; + /*bcastSrcDevice, corresponding PAResultsMap*/ + private Map mPAResultsMap; + public class PAResults { + public BluetoothDevice mDevice; + public int mAddressType; + public int mAdvSid; + public int mSyncHandle; + public byte metaDataLength; + public byte[] metaData; + public int mPAInterval; + public int mBroadcastId; + + PAResults(BluetoothDevice device, int addressType, + int syncHandle, int advSid, int paInterval, int broadcastId) { + mDevice = device; + mAddressType = addressType; + mAdvSid = advSid; + mSyncHandle = syncHandle; + mPAInterval = paInterval; + mBroadcastId = broadcastId; + } + + public void updateSyncHandle(int syncHandle) { + mSyncHandle = syncHandle; + } + + public void updateAdvSid(int advSid) { + mAdvSid = advSid; + } + + public void updateAddressType(int addressType) { + mAddressType = addressType; + } + + public void updateAdvInterval(int advInterval) { + mPAInterval = advInterval; + } + + public void updateBroadcastId(int broadcastId) { + mBroadcastId = broadcastId; + } + + public void print() { + log("-- PAResults --"); + log("mDevice:" + mDevice); + log("mAddressType:" + mAddressType); + log("mAdvSid:" + mAdvSid); + log("mSyncHandle:" + mSyncHandle); + log("mPAInterval:" + mPAInterval); + log("mBroadcastId:" + mBroadcastId); + log("-- END: PAResults --"); + } + }; + + public void updatePAResultsMap(BluetoothDevice device, int addressType, int syncHandle, int advSid, int advInterval, int bId) { + log("updatePAResultsMap: device: " + device); + log("updatePAResultsMap: syncHandle: " + syncHandle); + log("updatePAResultsMap: advSid: " + advSid); + log("updatePAResultsMap: addressType: " + addressType); + log("updatePAResultsMap: advInterval: " + advInterval); + log("updatePAResultsMap: broadcastId: " + bId); + log("mSyncHandleMap" + mSyncHandleMap); + log("mPAResultsMap" + mPAResultsMap); + //Cache the SyncHandle + if (mSyncHandleMap != null) { + Integer i = new Integer(syncHandle); + mSyncHandleMap.put(device, i); + } + if (mPAResultsMap != null) { + PAResults paRes = mPAResultsMap.get(device); + if (paRes == null) { + log("PAResmap: add >>>"); + paRes = new PAResults (device, addressType, + syncHandle, advSid, advInterval, bId); + if (paRes != null) { + paRes.print(); + mPAResultsMap.put(device, paRes); + } + } else { + if (advSid != INVALID_ADV_SID) { + paRes.updateAdvSid(advSid); + } + if (syncHandle != INVALID_SYNC_HANDLE) { + paRes.updateSyncHandle(syncHandle); + } + if (addressType != INVALID_ADV_ADDRESS_TYPE) { + paRes.updateAddressType(addressType); + } + if (advInterval != INVALID_ADV_INTERVAL) { + paRes.updateAdvInterval(advInterval); + } + if (bId != INVALID_BROADCAST_ID) { + paRes.updateBroadcastId(bId); + } + log("PAResmap: update >>>"); + paRes.print(); + mPAResultsMap.replace(device, paRes); + } + } + log(">>mPAResultsMap" + mPAResultsMap); + } + + public PAResults getPAResults(BluetoothDevice device) { + PAResults res = null; + if (mPAResultsMap != null) { + res = mPAResultsMap.get(device); + } else { + Log.e(TAG, "getPAResults: mPAResultsMap is null"); + } + return res; + } + public PAResults clearPAResults(BluetoothDevice device) { + PAResults res = null; + if (mPAResultsMap != null) { + res = mPAResultsMap.remove(device); + } else { + Log.e(TAG, "getPAResults: mPAResultsMap is null"); + } + return res; + } + + public void updateBASE(int syncHandlemap, BaseData base) { + if (mSyncHandleVsBaseInfo != null) { + log("updateBASE : mSyncHandleVsBaseInfo>>"); + mSyncHandleVsBaseInfo.put(syncHandlemap, base); + } else { + Log.e(TAG, "updateBASE: mSyncHandleVsBaseInfo is null"); + } + } + + public BaseData getBASE(int syncHandlemap) { + BaseData base = null; + if (mSyncHandleVsBaseInfo != null) { + log("getBASE : syncHandlemap::" + syncHandlemap); + base = mSyncHandleVsBaseInfo.get(syncHandlemap); + } else { + Log.e(TAG, "getBASE: mSyncHandleVsBaseInfo is null"); + } + log("getBASE returns" + base); + return base; + } + + public void clearBASE(int syncHandlemap) { + if (mSyncHandleVsBaseInfo != null) { + log("clearBASE : mSyncHandleVsBaseInfo>>"); + mSyncHandleVsBaseInfo.remove(syncHandlemap); + } else { + Log.e(TAG, "updateBASE: mSyncHandleVsBaseInfo is null"); + } + } + + public void setActiveSyncedSource(BluetoothDevice scanDelegator, BluetoothDevice sourceDevice) { + log("setActiveSyncedSource: scanDelegator" + scanDelegator + ":: sourceDevice:" + sourceDevice); + if (sourceDevice == null) { + mActiveSourceMap.remove(scanDelegator); + } else { + mActiveSourceMap.put(scanDelegator, sourceDevice); + } + } + + public BluetoothDevice getActiveSyncedSource(BluetoothDevice scanDelegator) { + BluetoothDevice currentSource = mActiveSourceMap.get(scanDelegator); + log("getActiveSyncedSource: scanDelegator" + scanDelegator + "returning " + currentSource); + return currentSource; + } + + @Override + protected IProfileServiceBinder initBinder() { + return new BluetoothSyncHelperBinder(this); + } + + //*_CSIP + private BluetoothGroupCallback mBluetoothGroupCallback = new BluetoothGroupCallback() { + public void onGroupClientAppRegistered(int status, int appId) { + log("onCsipAppRegistered:" + status + "appId: " + appId); + if (status == 0) { + mCsipAppId = appId; + } else { + Log.e(TAG, "Csip registeration failed, status:" + status); + } + } + + public void onConnectionStateChanged (int state, BluetoothDevice device) { + log("onConnectionStateChanged: Device: " + device + "state: " + state); + //notify the statemachine about CSIP connection + synchronized (mStateMachines) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + Message m = stateMachine.obtainMessage(BassClientStateMachine.CSIP_CONNECTION_STATE_CHANGED); + m.obj = state; + stateMachine.sendMessage(m); + } + } + + public void onNewGroupFound (int setId, BluetoothDevice device, UUID uuid) { } + public void onGroupDiscoveryStatusChanged (int setId, int status, int reason) { } + public void onGroupDeviceFound (int setId, BluetoothDevice device) { } + public void onExclusiveAccessChanged (int setId, int value, int status, List devices) { + log("onLockStatusChanged: setId" + setId + devices + "status:" + status); + BassCsetManager setMgr = null; + setMgr = getOrCreateCSetManager(setId, null); + if (setMgr == null) { + return; + } + log ("sending Lock status to setId:" + setId); + Message m = setMgr.obtainMessage(BassCsetManager.LOCK_STATE_CHANGED); + m.obj = devices; + m.arg1 = value; + setMgr.sendMessage(m); + } + public void onExclusiveAccessStatusFetched (int setId, int lockStatus) { } + public void onExclusiveAccessAvailable (int setId, BluetoothDevice device) { } + }; + //_CSIP*/ + + @Override + protected boolean start() { + if (DBG) { + Log.d(TAG, "start()"); + } + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when BCService starts"); + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("BCService.StateMachines"); + mStateMachinesThread.start(); + + mSetManagers.clear(); + mSetManagerThread = new HandlerThread("BCService.SetManagers"); + mSetManagerThread.start(); + + setBCService(this); + bassUtils = new BassUtils(this); + //Saving PSync stuff for future addition + mSyncHandleMap = new HashMap(); + mPAResultsMap = new HashMap(); + mSyncHandleVsBaseInfo = new HashMap(); + mActiveSourceMap = new HashMap(); + + //*_CSIP + //CSET initialization + mSetCoordinator = GroupService.getGroupService(); + if (mSetCoordinator != null) { + mSetCoordinator.registerGroupClientModule(mBluetoothGroupCallback); + } + //_CSIP*/ + /*_PACS + mPacsClientService = PacsClientService.getPacsClientService(); + _PACS*/ + + ///*_GAP + //GAP registeration for Bass UUID notification + if (mAdapterService != null) { + log("register for BASS UUID notif"); + ParcelUuid bassUuid = new ParcelUuid(BassClientStateMachine.BASS_UUID); + mAdapterService.registerUuidSrvcDisc(bassUuid); + } + //_GAP*/ + return true; + } + + @Override + protected boolean stop() { + if (DBG) { + Log.d(TAG, "stop()"); + } + + synchronized (mStateMachines) { + for (BassClientStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + + if (mStateMachinesThread != null) { + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + } + + if (mSetManagerThread != null) { + mSetManagerThread.quitSafely(); + mSetManagerThread = null; + } + + setBCService(null); + + if (mAppCallbackMap != null) { + mAppCallbackMap.clear(); + mAppCallbackMap = null; + } + + if (mSyncHandleMap != null) { + mSyncHandleMap.clear(); + mSyncHandleMap = null; + } + + if (mActiveSourceMap != null) { + mActiveSourceMap.clear(); + mActiveSourceMap = null; + } + //*_CSIP + if (mSetCoordinator != null && mCsipAppId != -1) { + //mSetCoordinator.unregisterGroupClientModule(mCsipAppId); + } + //_CSIP*/ + return true; + } + + @Override + public boolean onUnbind(Intent intent) { + Log.d(TAG, "Need to unregister app"); + //unregisterApp(); + return super.onUnbind(intent); + } + + /** + * Get the BCService instance + * @return BCService instance + */ + public static synchronized BCService getBCService() { + if (sBCService == null) { + Log.w(TAG, "getBCService(): service is NULL"); + return null; + } + + if (!sBCService.isAvailable()) { + Log.w(TAG, "getBCService(): service is not available"); + return null; + } + return sBCService; + } + + public BassUtils getBassUtils() { + return bassUtils; + } + + public BluetoothDevice getDeviceForSyncHandle(int syncHandle) { + BluetoothDevice dev = null; + if (mSyncHandleMap != null) { + for (Map.Entry entry : mSyncHandleMap.entrySet()) { + Integer value = entry.getValue(); + if (value == syncHandle) { + dev = entry.getKey(); + } + } + } + return dev; + } + + private static synchronized void setBCService(BCService instance) { + if (DBG) { + Log.d(TAG, "setBCService(): set to: " + instance); + } + sBCService = instance; + } + + /** + * Connects the bass profile to the passed in device + * + * @param device is the device with which we will connect the Bass profile + * @return true if BAss profile successfully connected, false otherwise + */ + public boolean connect(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "connect(): " + device); + } + if (device == null) { + return false; + } + + if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) { + return false; + } + synchronized (mStateMachines) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + + stateMachine.sendMessage(BassClientStateMachine.CONNECT); + } + return true; + } + + /** + * Disconnects Bassclient profile for the passed in device + * + * @param device is the device with which we want to disconnected the BAss client profile + * @return true if Bass client profile successfully disconnected, false otherwise + */ + public boolean disconnect(BluetoothDevice device) { + + if (DBG) { + Log.d(TAG, "disconnect(): " + device); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + + stateMachine.sendMessage(BassClientStateMachine.DISCONNECT); + } + return true; + } + + List getConnectedDevices() { + + synchronized (mStateMachines) { + List devices = new ArrayList<>(); + for (BassClientStateMachine sm : mStateMachines.values()) { + if (sm.isConnected()) { + devices.add(sm.getDevice()); + } + } + log("getConnectedDevices: " + devices); + return devices; + } + } + + /** + * Check whether can connect to a peer device. + * The check considers a number of factors during the evaluation. + * + * @param device the peer device to connect to + * @return true if connection is allowed, otherwise false + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean okToConnect(BluetoothDevice device) { + // Check if this is an incoming connection in Quiet mode. + if (mAdapterService.isQuietModeEnabled()) { + Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); + return false; + } + // Check connection policy and accept or reject the connection. + int connectionPolicy = getConnectionPolicy(device); + int bondState = mAdapterService.getBondState(device); + // Allow this connection only if the device is bonded. Any attempt to connect while + // bonding would potentially lead to an unauthorized connection. + if (bondState != BluetoothDevice.BOND_BONDED) { + Log.w(TAG, "okToConnect: return false, bondState=" + bondState); + return false; + } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + // Otherwise, reject the connection if connectionPolicy is not valid. + Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); + return false; + } + return true; + } + + List getDevicesMatchingConnectionStates(int[] states) { + + ArrayList devices = new ArrayList<>(); + if (states == null) { + return devices; + } + final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); + if (bondedDevices == null) { + return devices; + } + synchronized (mStateMachines) { + for (BluetoothDevice device : bondedDevices) { + final ParcelUuid[] featureUuids = device.getUuids(); + if (!ArrayUtils.contains(featureUuids, new ParcelUuid(BassClientStateMachine.BASS_UUID))) { + continue; + } + int connectionState = BluetoothProfile.STATE_DISCONNECTED; + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm != null) { + connectionState = sm.getConnectionState(); + } + for (int state : states) { + if (connectionState == state) { + devices.add(device); + break; + } + } + } + return devices; + } + } + + public int getConnectionState(BluetoothDevice device) { + synchronized (mStateMachines) { + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + log("getConnectionState returns STATE_DISC"); + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + /** + * Set the connectionPolicy of the Hearing Aid profile. + * + * @param device the remote device + * @param connectionPolicy the connection policy of the profile + * @return true on success, otherwise false + */ + public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + + if (DBG) { + Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); + } + boolean setSuccessfully; + setSuccessfully = mAdapterService.getDatabase() + .setProfileConnectionPolicy(device, BluetoothProfile.BC_PROFILE, connectionPolicy); + if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + connect(device); + } else if (setSuccessfully + && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + disconnect(device); + } + return setSuccessfully; + } + + /** + * Get the connection policy of the profile. + * + *

The connection policy can be any of: + * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, + * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + public int getConnectionPolicy(BluetoothDevice device) { + + return mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.BC_PROFILE); + } + + public void sendBroadcastSourceSelectedCallback(BluetoothDevice device, List bChannels, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceSelected(device, status, bChannels); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling sendBroadcastSourceSelectedCallback"); + } + } + } + + public void sendAddBroadcastSourceCallback(BluetoothDevice device, byte srcId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceAdded(device, srcId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastAudioSourceAdded"); + } + } + } + + public void sendUpdateBroadcastSourceCallback(BluetoothDevice device, byte sourceId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceUpdated(device, sourceId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastAudioSourceUpdated"); + } + } + } + public void sendRemoveBroadcastSourceCallback(BluetoothDevice device, byte sourceId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceRemoved(device, sourceId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastAudioSourceRemoved"); + } + } + } + public void sendSetBroadcastPINupdatedCallback(BluetoothDevice device, byte sourceId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastPinUpdated(device, sourceId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastPinUpdated"); + } + } + } + + public void registerAppCallback (BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + + Log.i(TAG, "registerAppCallback" + device); + + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.i(TAG, "registerAppCallback: entry exists"); + cbs = new ArrayList(); + } + cbs.add(cb); + mAppCallbackMap.put(device, cbs); + return; + } + + public void unregisterAppCallback (BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + + Log.i(TAG, "unregisterAppCallback" + device); + + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.i(TAG, "unregisterAppCallback: cb list is null"); + return; + } else { + boolean ret = cbs.remove(cb); + Log.i(TAG, "unregisterAppCallback: ret value of removal from list:" + ret); + } + if (cbs.size() != 0) { + mAppCallbackMap.replace(device, cbs); + } else { + Log.i(TAG, "unregisterAppCallback: Remove the cmplete entry"); + mAppCallbackMap.remove(device); + } + return; + } + + public boolean searchforLeAudioBroadcasters (BluetoothDevice device) { + + Log.i(TAG, "searchforLeAudioBroadcasters on behalf of" + device); + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return false; + } + boolean ret = false; + if (bassUtils != null) { + ret = bassUtils.searchforLeAudioBroadcasters(device, cbs); + } else { + Log.e(TAG, "searchforLeAudioBroadcasters :Null Bass Util Handle" + device); + ret = false; + } + return ret; + } + + public boolean stopSearchforLeAudioBroadcasters (BluetoothDevice device) { + + Log.i(TAG, "stopsearchforLeAudioBroadcasters on behalf of" + device); + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + } + boolean ret = false; + if (bassUtils != null) { + ret = bassUtils.stopSearchforLeAudioBroadcasters(device, cbs); + } else { + Log.e(TAG, "stopsearchforLeAudioBroadcasters :Null Bass Util Handle" + device); + ret = false; + } + return ret; + } + + public boolean selectBroadcastSource (BluetoothDevice device, ScanResult scanRes, boolean isGroupOp, boolean auto) { + + Log.i(TAG, "selectBroadcastSource for " + device + "isGroupOp:" + isGroupOp); + Log.i(TAG, "ScanResult " + scanRes); + + if (scanRes == null) { + Log.e(TAG, "selectBroadcastSource: null Scan results"); + return false; + } + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + if (isRoomForBroadcastSourceAddition(listOfDevices) == false) { + sendBroadcastSourceSelectedCallback(device, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT); + return false; + } + //dummy BleSourceInfo from scanRes + BleBroadcastSourceInfo scanResSI = new BleBroadcastSourceInfo (scanRes.getDevice(), + BassClientStateMachine.INVALID_SRC_ID, + (byte)scanRes.getAdvertisingSid(), + BleBroadcastSourceInfo.BROADCASTER_ID_INVALID, + scanRes.getAddressType(), + BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_INVALID, + BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_INVALID, + null, + (byte)0, + BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, + null, + null); + if (isValidBroadcastSourceAddition(listOfDevices, scanResSI) == false) { + sendBroadcastSourceSelectedCallback(device, + null, BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION); + return false; + } + startScanOffloadInternal(device, isGroupOp); + synchronized (mStateMachines) { + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + return false; + } + Message m = sm.obtainMessage(BassClientStateMachine.SELECT_BCAST_SOURCE); + m.obj = scanRes; + if (auto) { + m.arg1 = sm.AUTO; + } else { + m.arg1 = sm.USER; + } + if (isGroupOp) { + m.arg2 = sm.GROUP_OP; + } else { + m.arg2 = sm.NON_GROUP_OP; + } + sm.sendMessage(m); + } + return true; + } + + public synchronized void notifyOperationCompletion(BluetoothDevice device, int pendingOperation) { + log("notifyOperationCompletion: " + device + "pendingOperation: " + + BassClientStateMachine.messageWhatToString(pendingOperation)); + //synchronized (mStateMachines) { + switch (pendingOperation) { + case BassClientStateMachine.START_SCAN_OFFLOAD: + case BassClientStateMachine.STOP_SCAN_OFFLOAD: + case BassClientStateMachine.ADD_BCAST_SOURCE: + case BassClientStateMachine.UPDATE_BCAST_SOURCE: + case BassClientStateMachine.REMOVE_BCAST_SOURCE: + case BassClientStateMachine.SET_BCAST_CODE: + if (mQueuedOps > 0) { + mQueuedOps = mQueuedOps - 1; + } else { + log("not a queued op, Internal op"); + return; + } + break; + default: + { + log("notifyOperationCompletion: unhandled case"); + return; + } + } + //} + if (mQueuedOps == 0) { + log("notifyOperationCompletion: all ops are done!"); + //trigger unlock with last device + triggerUnlockforCSet(device); + } + + } + + public synchronized boolean startScanOffload (BluetoothDevice masterDevice, List devices) { + + Log.i(TAG, "startScanOffload for " + devices); + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + continue; + } + stateMachine.sendMessage(BassClientStateMachine.START_SCAN_OFFLOAD); + mQueuedOps = mQueuedOps + 1; + } + return true; + } + + public synchronized boolean stopScanOffload (BluetoothDevice masterDevice, List devices) { + + Log.i(TAG, "stopScanOffload for " + devices); + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + continue; + } + stateMachine.sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + public boolean isLocalBroadcasting() { + return bassUtils.isLocalLEAudioBroadcasting(); + } + + private boolean isValidBroadcastSourceAddition(List devices, + BleBroadcastSourceInfo srcInfo) { + boolean ret = true; + + //run through all the device, if it is not valid + //to even one device to add this source, return failure + for (BluetoothDevice dev : devices) { + List currentSourceInfos = + getAllBroadcastSourceInformation(dev); + if (currentSourceInfos == null) { + log("currentSourceInfos is null for " + dev); + continue; + } + for (int i=0; i devices) { + boolean isRoomAvail = false; + + //run through all the device, if it is not valid + //to even one device to add this source, return failure + for (BluetoothDevice dev : devices) { + isRoomAvail = false; + List currentSourceInfos = + getAllBroadcastSourceInformation(dev); + for (int i=0; i devices, BleBroadcastSourceInfo srcInfo + ) { + + Log.i(TAG, "addBroadcastSource for " + devices + + "SourceInfo " + srcInfo); + if (srcInfo == null) { + Log.e(TAG, "addBroadcastSource: null SrcInfo"); + return false; + } + if (isRoomForBroadcastSourceAddition(devices) == false) { + sendAddBroadcastSourceCallback(masterDevice, + BassClientStateMachine.INVALID_SRC_ID, BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT); + triggerUnlockforCSet(masterDevice); + return false; + } + + if (isValidBroadcastSourceAddition(devices, srcInfo) == false) { + sendAddBroadcastSourceCallback(masterDevice, + BassClientStateMachine.INVALID_SRC_ID, BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION); + triggerUnlockforCSet(masterDevice); + return false; + } + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "addBroadcastSource: device seem to be not avaiable, proceed"); + continue; + } + Message m = stateMachine.obtainMessage(BassClientStateMachine.ADD_BCAST_SOURCE); + m.obj = srcInfo; + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + return true; + } + + private byte getSrcIdForCSMember(BluetoothDevice masterDevice, BluetoothDevice memberDevice, byte masterSrcId) { + byte targetSrcId = -1; + List masterSrcInfos = getAllBroadcastSourceInformation(masterDevice); + List memberSrcInfos = getAllBroadcastSourceInformation(memberDevice); + if (masterSrcInfos == null || masterSrcInfos.size() == 0 || + memberSrcInfos == null || memberSrcInfos.size() == 0) { + Log.e(TAG, "master or member source Infos not available"); + return targetSrcId; + } + if (masterDevice.equals(memberDevice)) { + log("master: " + masterDevice + "member:memberDevice"); + return masterSrcId; + } + BluetoothDevice masterSrcDevice = null; + for (int i=0; i devices, + BleBroadcastSourceInfo srcInfo + ) { + + int status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + Log.i(TAG, "updateBroadcastSource for " + devices + + "masterDevice " + masterDevice + + "SourceInfo " + srcInfo); + + if (srcInfo == null) { + Log.e(TAG, "updateBroadcastSource: null SrcInfo"); + return false; + } + + for (BluetoothDevice dev : devices) { + if (getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()) == -1) { + if (devices.size() > 1) { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP; + } else { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + } + sendRemoveBroadcastSourceCallback(masterDevice, BassClientStateMachine.INVALID_SRC_ID, + status); + triggerUnlockforCSet(masterDevice); + return false; + } + } + + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "updateBroadcastSource: Device seem to be not avaiable"); + continue; + } + byte targetSrcId = getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()); + srcInfo.setSourceId(targetSrcId); + + Message m = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); + m.obj = srcInfo; + m.arg1 = stateMachine.USER; + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + public synchronized boolean setBroadcastCode (BluetoothDevice masterDevice, List devices, + BleBroadcastSourceInfo srcInfo + ) { + + int status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + Log.i(TAG, "setBroadcastCode for " + devices + + "masterDevice" + masterDevice + + "Broadcast PIN" + srcInfo.getBroadcastCode()); + + if (srcInfo == null) { + Log.e(TAG, "setBroadcastCode: null SrcInfo"); + return false; + } + + for (BluetoothDevice dev : devices) { + if (getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()) == -1) { + if (devices.size() > 1) { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP; + } else { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + } + sendRemoveBroadcastSourceCallback(masterDevice, BassClientStateMachine.INVALID_SRC_ID, + status); + triggerUnlockforCSet(masterDevice); + return false; + } + } + + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "setBroadcastCode: Device seem to be not avaiable"); + continue; + } + + byte targetSrcId = getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()); + srcInfo.setSourceId(targetSrcId); + + Message m = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE); + m.obj = srcInfo; + m.arg1 = stateMachine.FRESH; + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + public synchronized boolean removeBroadcastSource (BluetoothDevice masterDevice, List devices, + byte sourceId + ) { + + int status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + Log.i(TAG, "removeBroadcastSource for " + devices + + "masterDevice " + masterDevice + + "removeBroadcastSource: sourceId:" + sourceId); + + if (sourceId == BassClientStateMachine.INVALID_SRC_ID) { + Log.e(TAG, "removeBroadcastSource: Invalid source Id"); + return false; + } + + + for (BluetoothDevice dev : devices) { + if (getSrcIdForCSMember(masterDevice, dev, sourceId) == -1) { + if (devices.size() > 1) { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP; + } else { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + } + sendRemoveBroadcastSourceCallback(masterDevice, BassClientStateMachine.INVALID_SRC_ID, + status); + triggerUnlockforCSet(masterDevice); + return false; + } + } + + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "setBroadcastCode: Device seem to be not avaiable"); + continue; + } + + Message m = stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE); + m.arg1 = getSrcIdForCSMember(masterDevice, dev, sourceId); + log("removeBroadcastSource: send message to SM " + dev); + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + void triggerUnlockforCSet (BluetoothDevice device) { + //get setId + int setId = getCsetId(device); + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + Log.e(TAG, "triggerUnlockforCSet: setMgr is NULL"); + return; + } + //Sending UnLock to + log ("sending Unlock to device:" + device); + Message m = setMgr.obtainMessage(BassCsetManager.UNLOCK); + setMgr.sendMessage(m); + } + public List getAllBroadcastSourceInformation (BluetoothDevice device + ) { + Log.i(TAG, "getAllBroadcastSourceInformation for " + device); + synchronized (mStateMachines) { + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + return null; + } + return sm.getAllBroadcastSourceInformation(); + } + } + + private BassClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + BassClientStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + // Limit the maximum number of state machines to avoid DoS attack + if (mStateMachines.size() >= MAX_BASS_CLIENT_STATE_MACHINES) { + Log.e(TAG, "Maximum number of Bassclient state machines reached: " + + MAX_BASS_CLIENT_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = BassClientStateMachine.make(device, this, + mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + + private BassCsetManager getOrCreateCSetManager(int setId, BluetoothDevice masterDevice) { + if (setId == -1) { + Log.e(TAG, "getOrCreateCSetManager failed: invalid setId"); + return null; + } + synchronized (mSetManagers) { + BassCsetManager sm = mSetManagers.get(setId); + log("getOrCreateCSetManager: hashmap Entry:" + sm); + if (sm != null) { + return sm; + } + // Limit the maximum number of set manager state machines + if (mStateMachines.size() >= MAX_BASS_CLIENT_CSET_MEMBERS) { + Log.e(TAG, "Maximum number of Bassclient cset members reached: " + + MAX_BASS_CLIENT_CSET_MEMBERS); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new set Manager for " + setId); + } + sm = BassCsetManager.make(setId, masterDevice, this, + mSetManagerThread.getLooper()); + mSetManagers.put(setId, sm); + return sm; + } + } + + public boolean isLockSupportAvailable(BluetoothDevice device) { + boolean isLockAvail = false; + boolean forceNoCsip = SystemProperties.getBoolean("persist.vendor.service.bt.forceNoCsip", false); + if (forceNoCsip) { + log("forceNoCsip is set"); + return isLockAvail; + } + //*_CSIP + isLockAvail = mAdapterService.isGroupExclAccessSupport(device); + //_CSIP*/ + + log("isLockSupportAvailable for:" + device + "returns " + isLockAvail); + return isLockAvail; + } + + private int getCsetId(BluetoothDevice device) { + int setId = 1; + //*_CSIP + setId = mSetCoordinator.getRemoteDeviceGroupId(device, CAS_UUID); + //_CSIP*/ + log("getCsetId return:" + setId); + return setId; + } + public boolean stopScanOffloadInternal (BluetoothDevice device, boolean isGroupOp) { + boolean ret = false; + log("stopScanOffloadInternal: device: " + device + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_STOP_SCAN_OFFLOAD); + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = stopScanOffload(device, listOfDevices); + } + return ret; + } + + public boolean startScanOffloadInternal (BluetoothDevice device, boolean isGroupOp) { + boolean ret = false; + log("startScanOffloadInternal: device: " + device + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp&& isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_START_SCAN_OFFLOAD); + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = startScanOffload(device, listOfDevices); + } + return ret; + } + + public boolean addBroadcastSourceInternal (BluetoothDevice device, BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + boolean ret = false; + log("addBroadcastSourceInternal: device: " + device + + "srcInfo" + srcInfo + + "isGroupOp" + isGroupOp); + /* Even If the request is for Group, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_ADD_BCAST_SOURCE); + m.obj = srcInfo; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = addBroadcastSource(device, listOfDevices, srcInfo); + } + return ret; + } + + public boolean updateBroadcastSourceInternal (BluetoothDevice device, BleBroadcastSourceInfo srcInfo, + boolean isGroupOp + ) { + boolean ret = false; + log("updateBroadcastSourceInternal: device: " + device + + "srcInfo" + srcInfo + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_UPDATE_BCAST_SOURCE); + m.obj = srcInfo; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = updateBroadcastSource(device, listOfDevices, srcInfo); + } + return ret; + } + + protected boolean setBroadcastCodeInternal (BluetoothDevice device, BleBroadcastSourceInfo srcInfo, + boolean isGroupOp + ) { + boolean ret = false; + log("setBroadcastCodeInternal: device: " + device + + "srcInfo" + srcInfo + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_SET_BCAST_CODE); + m.obj = srcInfo; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = setBroadcastCode(device, listOfDevices, srcInfo); + } + return ret; + } + + public boolean removeBroadcastSourceInternal (BluetoothDevice device, byte sourceId, boolean isGroupOp + ) { + boolean ret = false; + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_REMOVE_BCAST_SOURCE); + m.arg1 = sourceId; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = removeBroadcastSource(device, listOfDevices, sourceId); + } + return ret; + } + + static void log(String msg) { + if (BassClientStateMachine.BASS_DBG) { + Log.d(TAG, msg); + } + } + + /** + * Binder object: must be a static class or memory leak may occur + */ + @VisibleForTesting + static class BluetoothSyncHelperBinder extends IBluetoothSyncHelper.Stub + implements IProfileServiceBinder { + private BCService mService; + + private BCService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + BluetoothSyncHelperBinder(BCService svc) { + mService = svc; + } + + @Override + public void cleanup() { + mService = null; + } + + @Override + public boolean connect(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.connect(device); + } + + @Override + public boolean disconnect(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.disconnect(device); + } + + @Override + public List getConnectedDevices() { + BCService service = getService(); + if (service == null) { + return new ArrayList<>(); + } + return service.getConnectedDevices(); + } + + @Override + public List getDevicesMatchingConnectionStates(int[] states) { + BCService service = getService(); + if (service == null) { + return new ArrayList<>(); + } + return service.getDevicesMatchingConnectionStates(states); + } + + @Override + public int getConnectionState(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return service.getConnectionState(device); + } + + @Override + public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + + @Override + public int getConnectionPolicy(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + } + return service.getConnectionPolicy(device); + } + @Override + public boolean searchforLeAudioBroadcasters (BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.searchforLeAudioBroadcasters(device); + } + + @Override + public boolean stopSearchforLeAudioBroadcasters (BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.stopSearchforLeAudioBroadcasters(device); + } + + @Override + public boolean selectBroadcastSource (BluetoothDevice device, ScanResult scanRes, boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.selectBroadcastSource(device, scanRes, isGroupOp, false); + } + + @Override + public void registerAppCallback(BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + BCService service = getService(); + if (service == null) { + return; + } + service.registerAppCallback(device, cb); + } + + @Override + public void unregisterAppCallback(BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + BCService service = getService(); + if (service == null) { + return; + } + service.unregisterAppCallback(device, cb); + } + + @Override + public boolean startScanOffload(BluetoothDevice device, boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.startScanOffloadInternal(device, isGroupOp); + } + + @Override + public boolean stopScanOffload(BluetoothDevice device, boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.stopScanOffloadInternal(device, isGroupOp); + } + + @Override + public boolean addBroadcastSource(BluetoothDevice device, BleBroadcastSourceInfo srcInfo + , boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.addBroadcastSourceInternal(device, srcInfo, isGroupOp); + } + + @Override + public boolean updateBroadcastSource (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.updateBroadcastSourceInternal(device, srcInfo, isGroupOp); + } + + @Override + public boolean setBroadcastCode (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.setBroadcastCodeInternal(device, srcInfo, isGroupOp); + } + + @Override + public boolean removeBroadcastSource (BluetoothDevice device, + byte sourceId, + boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.removeBroadcastSourceInternal(device, sourceId, isGroupOp); + } + @Override + public List getAllBroadcastSourceInformation (BluetoothDevice device + ) { + BCService service = getService(); + if (service == null) { + return null; + } + return service.getAllBroadcastSourceInformation(device); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java new file mode 100644 index 0000000000000000000000000000000000000000..b004afa669eaa0093fd9d0df640306a21acb7f86 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java @@ -0,0 +1,861 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package com.android.bluetooth.bc; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; + +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; + +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +///*_BMS +import com.android.bluetooth.broadcast.BroadcastService.BisInfo; +import com.android.bluetooth.broadcast.BroadcastService.MetadataLtv; +//_BMS*/ + +/** + * Helper class to parase the Broadcast Announcement BASE data + */ +final class BaseData { + private static final String TAG = "Bassclient-BaseData"; + BaseInformation levelOne = new BaseInformation(); + ArrayList levelTwo = new ArrayList(); + ArrayList levelThree = new ArrayList(); + int mNumBISIndicies; + public static byte UNKNOWN_CODEC = (byte)0xFE; + + public class BaseInformation { + public byte[] presentationDelay = new byte[3]; //valid only if level=1 + public byte[] codecId = new byte[5]; //valid only if level=1 + public byte codecConfigLength; + public byte[] codecConfigInfo; + public byte metaDataLength; + public byte[] metaData; + public byte numSubGroups; + public byte[] bisIndicies; //valid only if level = 2 + public byte index; //valid only if level=3 and level=2 (as subgroup Id) + public int subGroupId; + public int level;//differentiate different levels of BASE data + public LinkedHashSet keyCodecCfgDiff; + public LinkedHashSet keyMetadataDiff; + public String diffText; + public String description; + + public byte[] consolidatedCodecId; + public Set consolidatedMetadata; + public Set consolidatedCodecInfo; + public HashMap consolidatedUniqueCodecInfo; + public HashMap consolidatedUniqueMetadata; + + BaseInformation() { + presentationDelay = new byte[3]; + codecId = new byte[5]; + codecConfigLength = 0; + codecConfigInfo = null; + metaDataLength = 0; + metaData = null; + numSubGroups = 0; + bisIndicies = null; + index = (byte)0xFF; + level = 0; + + keyCodecCfgDiff = new LinkedHashSet(); + keyMetadataDiff = new LinkedHashSet(); + + consolidatedMetadata = new LinkedHashSet(); + consolidatedCodecInfo = new LinkedHashSet(); + consolidatedCodecId = new byte[5]; + consolidatedUniqueMetadata = new HashMap(); + consolidatedUniqueCodecInfo = new HashMap(); + diffText = new String(""); + description = new String(""); + log("BaseInformation is Initialized"); + } + + boolean isCodecIdUnknown() { + return (codecId != null && codecId[4] == (byte)BaseData.UNKNOWN_CODEC); + } + + void printConsolidated() { + log("**BEGIN: BIS consolidated Information**"); + log("BIS index:" + index); + log("CodecId:" + Arrays.toString(consolidatedCodecId)); + + /*if (consolidatedCodecInfo != null) { + Iterator itr = consolidatedCodecInfo.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("consolidatedCodecInfo:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + + if (consolidatedMetadata != null) { + Iterator itr = consolidatedMetadata.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("consolidatedMetadata:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + }*/ + + if (consolidatedUniqueCodecInfo != null) { + for (Map.Entry entry : consolidatedUniqueCodecInfo.entrySet()) { + log("consolidatedUniqueCodecInfo:[" + entry.getKey() + "]:" + Arrays.toString(entry.getValue().getBytes())); + } + } + + if (consolidatedUniqueMetadata != null) { + for (Map.Entry entry : consolidatedUniqueMetadata.entrySet()) { + log("consolidatedUniqueMetadata:[" + entry.getKey() + "]:" + Arrays.toString(entry.getValue().getBytes())); + } + } + log("**END: BIS consolidated Information****"); + } + void print() { + log("**BEGIN: Base Information**"); + log("**Level: " + level + "***"); + if (level == 1) { + log("presentationDelay: " + Arrays.toString(presentationDelay)); + } + if (level == 2) { + log("codecId: " + Arrays.toString(codecId)); + } + if (level == 2 || level == 3) { + log("codecConfigLength: " + codecConfigLength); + log("subGroupId: " + subGroupId); + } + if (codecConfigLength != (byte)0) { + log("codecConfigInfo: " + Arrays.toString(codecConfigInfo)); + } + if (level == 2) { + log("metaDataLength: " + metaDataLength); + if (metaDataLength != (byte)0) { + log("metaData: " + Arrays.toString(metaData)); + } + if (level == 1 || level == 2) + log("numSubGroups: " + numSubGroups); + } + if (level == 2) { + log("Level2: Key Metadata differentiators"); + if (keyMetadataDiff != null) { + Iterator itr = keyMetadataDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("keyMetadataDiff:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level2: Key Metadata differentiators"); + + log("Level2: Key CodecConfig differentiators"); + if (keyCodecCfgDiff != null) { + Iterator itr = keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("LEVEL2: keyCodecCfgDiff:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level2: Key CodecConfig differentiators"); + //log("bisIndicies: " + Arrays.toString(bisIndicies)); + log("LEVEL2: diffText: " + diffText); + } + if (level == 3) { + log("Level3: Key CodecConfig differentiators"); + if (keyCodecCfgDiff != null) { + Iterator itr = keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("LEVEL3: keyCodecCfgDiff:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level3: Key CodecConfig differentiators"); + log("index: " + index); + log("LEVEL3: diffText: " + diffText); + } + log("**END: Base Information****"); + } + }; + ///*_BMS + BaseData(int numSubGroups, List colocatedBisInfo, Map metaInfo) { + if (metaInfo == null || colocatedBisInfo == null) { + + Log.e(TAG, "BaseData Contruction with Invalid parameters"); + throw new IllegalArgumentException("Basedata: Parameters can't be null"); + } + levelOne = new BaseInformation(); + levelTwo = new ArrayList(); + levelThree = new ArrayList(); + + levelOne.level = 1; + levelOne.numSubGroups = (byte)numSubGroups; + + //create the level Two and update the Metadata Info + for (int i=0; i(); + levelThree = new ArrayList(); + mNumBISIndicies = 0; + log("members initialized"); + log("BASE input" + Arrays.toString(serviceData)); + + //Parse Level 1 base + levelOne.level = 1; + int level1Idx = 0; + System.arraycopy(serviceData, level1Idx, levelOne.presentationDelay,0, 3); + level1Idx = level1Idx + 3; + + levelOne.numSubGroups = serviceData[level1Idx++]; + levelOne.print(); + log("levelOne subgroups" + levelOne.numSubGroups); + + int level2Idx = level1Idx; + for (int i =0; i<(int)levelOne.numSubGroups; i++) { + log("parsing subgroup" + i); + BaseInformation b = new BaseInformation(); + + b.level = 2; + b.subGroupId = i; + b.numSubGroups = serviceData[level2Idx++]; + if (serviceData[level2Idx] == (byte)UNKNOWN_CODEC) { + //Place It in the last byte of codecID + System.arraycopy(serviceData, level2Idx, b.codecId, 4, 1); + level2Idx = level2Idx + 1; + log("codecId is FE"); + } else { + System.arraycopy(serviceData, level2Idx, b.codecId, 0, 5); + level2Idx = level2Idx + 5; + } + + b.codecConfigLength = serviceData[level2Idx++]; + if (b.codecConfigLength != 0) { + b.codecConfigInfo = new byte[(int)b.codecConfigLength]; + System.arraycopy(serviceData, level2Idx, b.codecConfigInfo, 0, (int)b.codecConfigLength); + level2Idx = level2Idx + (int)b.codecConfigLength; + } + b.metaDataLength = serviceData[level2Idx++]; + if (b.metaDataLength != 0) { + b.metaData = new byte[(int)b.metaDataLength]; + System.arraycopy(serviceData, level2Idx, b.metaData, 0, (int)b.metaDataLength); + level2Idx = level2Idx + (int)b.metaDataLength; + } + mNumBISIndicies = mNumBISIndicies + b.numSubGroups; + levelTwo.add(b); + b.print(); + } + //Parse Level 3 Base + int level3Index = level2Idx; + for (int k=0; k uniqueMds = new HashMap (); + Map uniqueCcis = new HashMap (); + + Set Csfs = levelThree.get(i).consolidatedCodecInfo; + + if (Csfs.size() > 0) { + Iterator itr = Csfs.iterator(); + for (int j=0; itr.hasNext(); j++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + + // + int type = (int)ltv[1]; + String s = uniqueCcis.get(type); + String ltvS = new String(ltv); + if (s == null) { + uniqueCcis.put(type, ltvS); + } else { + //if same type exists + //replace + uniqueCcis.replace(type, ltvS); + } + } + } + + Set Mds = levelThree.get(i).consolidatedMetadata; + if (Mds.size() > 0) { + Iterator itr = Mds.iterator(); + for (int j=0; itr.hasNext(); j++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + + /*CHECK: This can be straight PUT, there wont be dups in Metadata with new BASE*/ + int type = (int)ltv[1]; + String s = uniqueCcis.get(type); + String ltvS = new String(ltv); + if (s == null) { + uniqueMds.put(type, ltvS); + } else { + //if same type exists + //replace + uniqueMds.replace(type, ltvS); + } + } + } + + levelThree.get(i).consolidatedUniqueMetadata = new HashMap(uniqueMds); + levelThree.get(i).consolidatedUniqueCodecInfo = new HashMap(uniqueCcis); + + } + } + + void consolidateBaseofLevelThree(int parentSubgroup, int startIdx, int numNodes) { + + for (int i=startIdx; i(levelTwo.get(parentSubgroup).consolidatedMetadata); + + //log("Parent Cons Info>>"); + //levelTwo.get(parentSubgroup).printConsolidated(); + //CCI clone from Parent + levelThree.get(i).consolidatedCodecInfo = new LinkedHashSet(levelTwo.get(parentSubgroup).consolidatedCodecInfo); + //log("before " + i); + //levelThree.get(i).printConsolidated(); + //Append Level 2 Codec Config + if (levelThree.get(i).codecConfigLength != 0) { + log("append level 3 cci to level 3 cons:" + i); + String s = new String(levelThree.get(i).codecConfigInfo); + levelThree.get(i).consolidatedCodecInfo.add(s); + } + //log("after " + i); + //levelThree.get(i).printConsolidated(); + //log("Parent Cons Info>>"); + //levelTwo.get(parentSubgroup).printConsolidated(); + } + + } + + public int getNumberOfIndicies() { + return mNumBISIndicies; + } + + public byte getNumberOfSubgroupsofBIG() { + byte ret = 0; + if (levelOne != null) { + ret = levelOne.numSubGroups; + } + return ret; + } + + public ArrayList getBISIndexInfos() { + return levelThree; + } + List getBroadcastChannels() { + List bChannels = new ArrayList(); + for (int k=0; k pickAllBroadcastChannels() { + List bChannels = new ArrayList(); + for (int k=0; k itr = levelTwo.get(i).keyMetadataDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat(getMetadataString(itr.next().getBytes())); + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat("_"); + } + } + if (levelTwo.get(i).keyCodecCfgDiff != null) { + Iterator itr = levelTwo.get(i).keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat(getCodecParamString(itr.next().getBytes())); + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat("_"); + } + } + } + + for (int i=0; i itr = levelThree.get(i).keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + levelThree.get(i).diffText = levelThree.get(i).diffText.concat(getCodecParamString(itr.next().getBytes())); + levelThree.get(i).diffText = levelThree.get(i).diffText.concat("_"); + } + } + } + + //Concat and update the Description + int startIdx = 0; + int children = 0; + for (int i=0; i uniqueCodecIds = new LinkedHashSet(); + Set uniqueCsfs = new LinkedHashSet(); + Set uniqueMetadatas = new LinkedHashSet(); + + log("updateUniquenessForLevelTwo"); + + int startIdx = 0; + int children = 0; + for (int i=0; i uniqueCodecParams = new LinkedHashSet(); + Set uniqueMetadataParams = new LinkedHashSet(); + + if (uniqueCodecIds.size() > 0) log("LevelTwo: UniqueCodecIds"); + + if (uniqueCsfs.size() > 0) { + log("LevelTwo: uniqueCsfs"); + //uniqueCodecParams = + Iterator itr = uniqueCsfs.iterator(); + for (int i=0; itr.hasNext(); i++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + //This should ensure Duplicate entries at this level + String s = new String(ltvEntries); + uniqueCodecParams.add(s); + } + } + if (uniqueMetadatas.size() > 0) { + log("LevelTwo: uniqueMetadatas"); + //uniqueMetadataParams = new LinkedHashSet(); + Iterator itr = uniqueMetadatas.iterator(); + for (int i=0; itr.hasNext(); i++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + //This should ensure Duplicate entries at this level + String s = new String(ltvEntries); + uniqueMetadataParams.add(s); + } + } + + //run though the nodes and update KEY differentiating factors + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + int i = 0; + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).keyCodecCfgDiff.add(itr.next()); + i = (i+1)%(numNodes); + } + } + + //run though the nodes and update KEY differentiating factors + if (uniqueMetadataParams != null) { + Iterator itr = uniqueMetadataParams.iterator(); + int i = 0; + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).keyMetadataDiff.add(itr.next()); + i = (i+1)%(numNodes); + } + } + + /*log("Level2: Uniqueness among subgroups"); + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("UniqueCodecParams:[" + k + "]" + Arrays.toString(itr.next().getBytes())); + } + } + if (uniqueMetadataParams != null) { + Iterator itr = uniqueMetadataParams.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("uniqueMetadataParams:["+ k + "]" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level2: Uniqueness among subgroups"); + */ + } + void updateUniquenessForLevelThree(int parentSubgroup, int startIdx, int numNodes) { + //Set uniqueCodecIds = new LinkedHashSet(); + Set uniqueCsfs = new LinkedHashSet(); + //Set uniqueMetadatas = new LinkedHashSet(); + + log("updateUniquenessForLevelThree: startIdx" + startIdx + "numNodes" + numNodes); + for (int i=startIdx; i uniqueCodecParams = new LinkedHashSet(); + if (uniqueCsfs.size() > 0) { + log("LevelThree: uniqueCsfs"); + //uniqueCodecParams = + Iterator itr = uniqueCsfs.iterator(); + for (int i=0; itr.hasNext(); i++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + //This should ensure Duplicate entries at this level + String s = new String(ltvEntries); + uniqueCodecParams.add(s); + } + } + //run though the nodes and update KEY differentiating factors + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + int i = startIdx; + for (int k=0; itr.hasNext(); k++) { + levelThree.get(i).keyCodecCfgDiff.add(itr.next()); + i = (i+1)%(startIdx+numNodes); + } + } + /* + log("Level3: Uniqueness among children of " + parentSubgroup + "th Subgroup"); + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("UniqueCodecParams:[" + k + "]" + Arrays.toString(itr.next().getBytes())); + + } + } + log("END: Level3: Uniqueness among children of " + parentSubgroup + "th Subgroup"); + */ + } + + void print() { + levelOne.print(); + log("----- Level TWO BASE ----"); + for (int i=0; i(Disconnecting) + * | ^ + * CONNECTED | | DISCONNECT + * V | + * (Connected) + * | ^ + * GATT_TXN | | GATT_TXN_DONE/GATT_TXN_TIMEOUT + * V | + * (ConnectedProcessing) + * NOTES: + * - If state machine is in "Connecting" state and the remote device sends + * DISCONNECT request, the state machine transitions to "Disconnecting" state. + * - Similarly, if the state machine is in "Disconnecting" state and the remote device + * sends CONNECT request, the state machine transitions to "Connecting" state. + * - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and + * all other requests (add, update, remove source) operations will be deferred in "ConnectedProcessing" state + * - Once the gatt transaction is done (or after a specified timeout of no response), State machine will + * move back to "Connected" and try to process the deferred requests as needed + * + * DISCONNECT + * (Connecting) ---------------> (Disconnecting) + * <--------------- + * CONNECT + * + */ + +package com.android.bluetooth.bc; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import com.android.bluetooth.Utils; + +//CSIP related imports +///*_CSIP +import com.android.bluetooth.groupclient.GroupService; +import android.bluetooth.BluetoothGroupCallback; +import android.bluetooth.DeviceGroup; +//_CSIP*/ + +///*_VCP +import android.bluetooth.BluetoothVcp; +import com.android.bluetooth.vcp.VcpController; +//_VCP*/ + +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; + +import android.bluetooth.IBluetoothManager; +import android.os.ServiceManager; +import android.os.IBinder; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; +import java.lang.IllegalArgumentException; + +import com.android.bluetooth.btservice.ProfileService; +/*_PACS +import com.android.bluetooth.pacsclient.PacsClientService; +_PACS*/ + +import com.android.bluetooth.btservice.ServiceFactory; +///*_BMS +import com.android.bluetooth.broadcast.BroadcastService; +//_BMS*/ + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import android.os.SystemProperties; +import android.os.ParcelUuid; + + +final class BassClientStateMachine extends StateMachine { + private static final String TAG = "BassClientStateMachine"; + public static final boolean BASS_DBG = true; + //public static final boolean BASS_DBG = Log.isLoggable(TAG, Log.DEBUG); + + private boolean mIsWhitelist = false; + + static final int BCAST_RECEIVER_STATE_LENGTH = 15; + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int CONNECTION_STATE_CHANGED = 3; + static final int GATT_TXN_PROCESSED = 4; + static final int READ_BASS_CHARACTERISTICS= 5; + static final int START_SCAN_OFFLOAD = 6; + static final int STOP_SCAN_OFFLOAD = 7; + static final int SELECT_BCAST_SOURCE = 8; + static final int ADD_BCAST_SOURCE = 9; + static final int UPDATE_BCAST_SOURCE = 10; + static final int SET_BCAST_CODE = 11; + static final int REMOVE_BCAST_SOURCE = 12; + static final int GATT_TXN_TIMEOUT = 13; + static final int PSYNC_ACTIVE_TIMEOUT = 14; + public static final int CSIP_CONNECTION_STATE_CHANGED = 15; + static final int CONNECT_TIMEOUT = 16; + + //30 secs time out for all gatt writes + static final int GATT_TXN_TIMEOUT_MS = 30000; + + //3 min time out for keeping PSYNC active + static final int PSYNC_ACTIVE_TIMEOUT_MS = 3*60000; + //2 secs time out achieving psync + static final int PSYNC_TIMEOUT = 200; + + int NUM_OF_BROADCAST_RECEIVER_STATES = 0; + + private final Disconnected mDisconnected; + private final Connected mConnected; + private final Connecting mConnecting; + private final Disconnecting mDisconnecting; + private final ConnectedProcessing mConnectedProcessing; + private int mLastConnectionState = -1; + private static int mConnectTimeoutMs = 30000; + private boolean mMTUChangeRequested = false; + private boolean mDiscoveryInitiated = false; + + private BCService mService; + private final BluetoothDevice mDevice; + private BluetoothGatt mBluetoothGatt = null; + + //BASS Characteristics UUID + public static final UUID BASS_UUID = UUID.fromString("0000184F-0000-1000-8000-00805F9B34FB"); + private static final UUID BASS_BCAST_AUDIO_SCAN_CTRL_POINT = UUID.fromString("00002BC7-0000-1000-8000-00805F9B34FB"); + private static final UUID BASS_BCAST_RECEIVER_STATE = UUID.fromString("00002BC8-0000-1000-8000-00805F9B34FB"); + private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString( + "00002902-0000-1000-8000-00805f9b34fb"); + private List mBroadcastReceiverStates; + private BluetoothGattCharacteristic mBroadcastScanControlPoint; + /*key is combination of sourceId, Address and advSid for this hashmap*/ + private final Map mBleBroadcastSourceInfos; + private boolean mFirstTimeBisDiscovery = false; + private int mPASyncRetryCounter = 0; + private ScanResult mScanRes = null; + + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private ServiceFactory mFactory = new ServiceFactory(); + ///*_BMS + private BroadcastService mBAService = null; + //_BMS*/ + + private final byte[] REMOTE_SCAN_STOP = {00}; + private final byte[] REMOTE_SCAN_START = {01}; + private byte BASS_ADD_SOURCE_OPCODE = 0x02; + private byte BASS_UPDATE_SOURCE_OPCODE = 0x03; + private byte BASS_SET_BCAST_PIN_OPCODE = 0x04; + private byte BASS_REMOVE_SOURCE_OPCODE = 0x05; + + private static int num_of_recever_states = 0; + private static int PIN_CODE_CMD_LEN = 18; + private final int BASS_MAX_BYTES = 100; + private int mPendingOperation = -1; + private byte mPendingSourceId = -1; + public static byte INVALID_SRC_ID = -1; + private int GATT_TXN_TOUT_ERROR = -1; + private BleBroadcastSourceInfo mSetBroadcastPINSrcInfo = null; + private boolean mSetBroadcastCodePending = false; + + //types of command for set broadcast PIN operation + public int FRESH = 1; + private int QUEUED = 2; + + //types of command for select and add Broadcast source operations + public int AUTO = 1; + public int USER = 2; + + //types of operation for Select source to determine + //if psync achieved on behalf of single device or multiple devices + public int GROUP_OP = 1; + public int NON_GROUP_OP = 0; + + public static int BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC = 0; + + //Service data Octet0 + private static int BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS = 0x00000001; + private static int BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS = 0x00000002; + + //Psync and PAST interfaces + private PeriodicAdvertisingManager mPeriodicAdvManager; + private static UUID BASIC_AUDIO_UUID = UUID.fromString("00001851-0000-1000-8000-00805F9B34FB"); + private boolean mAutoAssist = false; + private boolean mNoReverse = false; + private boolean mAutoTriggerred = false; + private boolean mSyncingOnBehalfOfGroup = false; + private boolean mNoStopScanOffload = false; + + //CSET interfaces + ///*_CSIP + private GroupService mSetCoordinator = GroupService.getGroupService(); + private boolean mCsipConnected = false; + //_CSIP*/ + + private boolean mPacsAvail = false; + private boolean mDefNoPAS = false; + private boolean mNoPast = false; + private boolean mNoCSIPReconn = false; + private boolean mPublicAddrForcSrc = false; + private boolean mForceSB = false; + private boolean mVcpForBroadcast = false; + + private int BROADCAST_SOURCE_ID_LENGTH = 3; + private byte mTempSourceId = 0; + //broadcast receiver state indicies + private static final int BCAST_RCVR_STATE_SRC_ID_IDX = 0; + private static final int BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX = 1; + private static final int BCAST_RCVR_STATE_SRC_ADDR_START_IDX = 2; + private static final int BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX = 9; + private static final int BCAST_RCVR_STATE_SRC_ADDR_SIZE = 6; + private static final int BCAST_RCVR_STATE_SRC_ADV_SID_IDX = 8; + private static final int BCAST_RCVR_STATE_PA_SYNC_IDX = 12; + private static final int BCAST_RCVR_STATE_ENC_STATUS_IDX = 13; + private static final int BCAST_RCVR_STATE_BADCODE_START_IDX = 14; + private static final int BCAST_RCVR_STATE_BADCODE_SIZE = 16; + + + private static final int BCAST_RCVR_STATE_BIS_SYNC_START_IDX = 10; + private static final int BCAST_RCVR_STATE_BIS_SYNC_SIZE = 4; + private static final int BCAST_RCVR_STATE_METADATA_LENGTH_IDX = 15; + private static final int BCAST_RCVR_STATE_METADATA_START_IDX = 16; + BassClientStateMachine(BluetoothDevice device, BCService svc, + Looper looper) { + super(TAG + "(" + device.toString() + ")", looper); + mDevice = device; + mService = svc; + + mDisconnected = new Disconnected(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + mConnecting = new Connecting(); + mConnectedProcessing = new ConnectedProcessing(); + + addState(mDisconnected); + addState(mDisconnecting); + addState(mConnected); + addState(mConnecting); + addState(mConnectedProcessing); + + setInitialState(mDisconnected); + mBroadcastReceiverStates = new ArrayList(); + mBleBroadcastSourceInfos = new HashMap(); + + //PSYNC and PAST instances + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter != null) { + mPeriodicAdvManager = mBluetoothAdapter.getPeriodicAdvertisingManager(); + } + ///*_BMS + mBAService = BroadcastService.getBroadcastService(); + //_BMS*/ + + mNoReverse = SystemProperties.getBoolean("persist.vendor.service.bt.nReverse", false); + mAutoAssist = SystemProperties.getBoolean("persist.vendor.service.bt.autoassist", false); + mIsWhitelist = SystemProperties.getBoolean("persist.vendor.service.bt.wl", true); + mDefNoPAS = SystemProperties.getBoolean("persist.vendor.service.bt.defNoPAS", false); + mNoPast = SystemProperties.getBoolean("persist.vendor.service.bt.noPast", false); + mNoCSIPReconn = SystemProperties.getBoolean("persist.vendor.service.bt.noCsipRec", false); + mPublicAddrForcSrc = SystemProperties.getBoolean("persist.vendor.service.bt.pAddrForcSource", true); + mForceSB = SystemProperties.getBoolean("persist.vendor.service.bt.forceSB", false); + mVcpForBroadcast = SystemProperties.getBoolean("persist.vendor.service.bt.vcpForBroadcast", true); + } + + static BassClientStateMachine make(BluetoothDevice device, BCService svc, + Looper looper) { + Log.d(TAG, "make for device " + device); + BassClientStateMachine BassclientSm = new BassClientStateMachine(device, svc, + looper); + BassclientSm.start(); + return BassclientSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + clearCharsCache(); + + if (mBluetoothGatt != null) { + log("disconnect gatt"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + mPendingOperation = -1; + mPendingSourceId = -1; + } + + BleBroadcastSourceInfo getBroadcastSourceInfoForSourceDevice (BluetoothDevice srcDevice) { + List currentSourceInfos = + getAllBroadcastSourceInformation(); + BleBroadcastSourceInfo srcInfo = null; + for (int i=0; i currentSourceInfos = + getAllBroadcastSourceInformation(); + BleBroadcastSourceInfo srcInfo = null; + for (int i=0; i bmsAdvDataMap = record.getServiceData(); + if (bmsAdvDataMap != null) { + for (Map.Entry entry : bmsAdvDataMap.entrySet()) { + log("ParcelUUid = " + entry.getKey() + + ", Value = " + entry.getValue()); + } + } else { + log("bmsAdvDataMap is null"); + if (mAutoTriggerred == false) { + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); + cancelActiveSync(srcDevice); + } else { + mAutoTriggerred = false; + } + } + ParcelUuid basicAudioUuid = new ParcelUuid(BASIC_AUDIO_UUID); + byte[] bmsAdvData = record.getServiceData(basicAudioUuid); + if (bmsAdvData != null) { + //ByteBuffer bb = ByteBuffer.wrap(bmsAdvData); + parseBaseData(mDevice, syncHandle, bmsAdvData); + + } else { + Log.e(TAG, "No service data in Scan record"); + if (mAutoTriggerred == false) { + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); + cancelActiveSync(srcDevice); + } else { + mAutoTriggerred = false; + } + } + } + + /*Local Public address based check + Use this prior to addition of Broadcast source*/ + boolean isLocalBroadcastSource (BluetoothDevice device) + { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + boolean ret = btAdapter.getAddress().equals(device.getAddress()); + + log("isLocalBroadcastSource returns" +ret); + return ret; + } + + private boolean isValidBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { + boolean ret = true; + List currentSourceInfos = + getAllBroadcastSourceInformation(); + Log.i(TAG, "input srcInfo: " + srcInfo); + for (int i=0; i listOfUuids = scanRecord.getServiceData(); + if (listOfUuids != null) { + if(listOfUuids.containsKey(ParcelUuid.fromString(BassUtils.BAAS_UUID))) { + byte[] bId = listOfUuids.get(ParcelUuid.fromString(BassUtils.BAAS_UUID)); + broadcastId = (0x00FF0000 & (bId[0] << 16)); + broadcastId |= (0x0000FF00 & (bId[1] << 8)); + broadcastId |= (0x000000FF & bId[2]); + } + } + mService.updatePAResultsMap(scanRes.getDevice(), scanRes.getAddressType(), + BCService.INVALID_SYNC_HANDLE, BCService.INVALID_ADV_SID, + scanRes.getPeriodicAdvertisingInterval(), + broadcastId); + } + } + else { + log("colocated case"); + if (autoTriggerred) { + log("should never happen!!!"); + //ignore + mAutoTriggerred = false; + } + ///*_BMS + if (mBAService == null || mBAService.isBroadcastActive() != true) { + Log.e(TAG, "colocated source handle unavailable OR not in streaming"); + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE); + mService.stopScanOffloadInternal(mDevice, false); + return false; + } + String colocatedAddress = null; + int colocatedAddressType; + if (mPublicAddrForcSrc == true) { + colocatedAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); + colocatedAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC; + } else { + colocatedAddress = mBAService.BroadcastGetAdvAddress(); + colocatedAddressType = mBAService.BroadcastGetAdvAddrType(); + } + int paInterval = 0x0000FFFF; + paInterval = mBAService.BroadcastGetAdvInterval(); + + if (colocatedAddress == null) { + log("colocatedAddress is null"); + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE); + mService.stopScanOffloadInternal(mDevice, false); + return false; + } + BluetoothDevice colocatedSrcDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(colocatedAddress); + log("caching local Broacast details: " + colocatedSrcDevice); + + //advSid is same as advHandle for collocated case + byte[] broadcast_id = mBAService.getBroadcastId(); + broadcastId = (0x00FF0000 & (broadcast_id[2] << 16)); + broadcastId |= (0x0000FF00 & (broadcast_id[1] << 8)); + broadcastId |= (0x000000FF & broadcast_id[0]); + + mService.updatePAResultsMap(colocatedSrcDevice, colocatedAddressType, + mBAService.BroadcatGetAdvHandle(), + mBAService.BroadcatGetAdvHandle(), + paInterval, + broadcastId); + BaseData localBase = new BaseData(mBAService.getNumSubGroups(), + mBAService.BroadcastGetBisInfo(), + mBAService.BroadcastGetMetaInfo()); + localBase.printConsolidated(); + //Use advHandle to cahce Base + mService.updateBASE(mBAService.BroadcatGetAdvHandle(), localBase); + mService.sendBroadcastSourceSelectedCallback(mDevice, localBase.getBroadcastChannels(), + BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); + //_BMS*/ + } + return true; + } + + private void cancelActiveSync(BluetoothDevice sourceDev) { + log("cancelActiveSync"); + boolean isCancelSyncNeeded = false; + BluetoothDevice activeSyncedSrc = mService.getActiveSyncedSource(mDevice); + if (activeSyncedSrc != null ) { + if (sourceDev == null) { + isCancelSyncNeeded = true; + } else if(activeSyncedSrc.equals(sourceDev)) { + isCancelSyncNeeded = true; + } + } + if (isCancelSyncNeeded) { + removeMessages(PSYNC_ACTIVE_TIMEOUT); + try { + log("calling unregisterSync"); + mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallback); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "unregisterSync:IllegalArguementException"); + //ignore + } + mService.clearPAResults(activeSyncedSrc); + mService.setActiveSyncedSource(mDevice, null); + if (mNoStopScanOffload != true) { + //trigger scan stop here + mService.stopScanOffloadInternal(mDevice, false); + } + } + mNoStopScanOffload = false; + } + + /* Internal periodc Advertising manager callback + * + */ + private PeriodicAdvertisingCallback mPeriodicAdvCallback = new PeriodicAdvertisingCallback() { + @Override + public void onSyncEstablished(int syncHandle, BluetoothDevice device, + int advertisingSid, int skip, int timeout, + int status) { + log ("onSyncEstablished" + "syncHandle" + syncHandle + + "device" + device + "advertisingSid" + advertisingSid + + "skip" + skip + "timeout" + timeout + "status" + + status); + //turn off the LeScan once sync estd + mService.getBassUtils().leScanControl(false); + if (status == BluetoothGatt.GATT_SUCCESS) { + //upates syncHandle, advSid + mService.updatePAResultsMap(device, + BCService.INVALID_ADV_ADDRESS_TYPE, + syncHandle, advertisingSid, + BCService.INVALID_ADV_INTERVAL, + BCService.INVALID_BROADCAST_ID); + sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT, PSYNC_ACTIVE_TIMEOUT_MS); + mService.setActiveSyncedSource(mDevice, device); + } else { + log("failed to sync to PA" + mPASyncRetryCounter); + mScanRes = null; + if (mAutoTriggerred == false) { + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE); + mService.stopScanOffloadInternal(mDevice, false); + } + mAutoTriggerred = false; + } + } + @Override + public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { + log( "onPeriodicAdvertisingReport"); + //Parse the BIS indicies from report's service data + if (mFirstTimeBisDiscovery) { + parseScanRecord(report.getSyncHandle(),report.getData()); + mFirstTimeBisDiscovery = false; + } + } + @Override + public void onSyncLost(int syncHandle) { + log( "OnSyncLost" + syncHandle); + BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle); + cancelActiveSync(srcDevice); + } + + public void onSyncTransfered(BluetoothDevice device, int status) { + log("sync transferred:" + device + " : " + status); + } + }; + + private void broadcastReceiverState(BleBroadcastSourceInfo state, int index, int max_num_srcInfos) { + log("broadcastReceiverState: " + mDevice); + + Intent intent = new Intent(BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.putExtra(BleBroadcastSourceInfo.EXTRA_SOURCE_INFO, state); + intent.putExtra(BleBroadcastSourceInfo.EXTRA_SOURCE_INFO_INDEX, index); + intent.putExtra(BleBroadcastSourceInfo.EXTRA_MAX_NUM_SOURCE_INFOS, max_num_srcInfos); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + private static boolean isEmpty(final byte[] data){ + return IntStream.range(0, data.length).parallel().allMatch(i -> data[i] == 0); + } + + private void processPASyncState(BleBroadcastSourceInfo srcInfo) { + log("processPASyncState" + srcInfo); + int serviceData = 0; + if (srcInfo == null) { + Log.e(TAG, "processPASyncState: srcInfo is null"); + return; + } + if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ) { + log("Initiate PAST procedure"); + BCService.PAResults res = mService.getPAResults(srcInfo.getSourceDevice()); + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice()) && + mService.isLocalBroadcasting()) { + if (res == null) { + log("Populate colocated PA and initiate PAST"); + + int colocatedAddressType; + if (mPublicAddrForcSrc == true) { + colocatedAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC; + } else { + colocatedAddressType = mBAService.BroadcastGetAdvAddrType(); + } + int broadcastId; + byte[] broadcast_id = mBAService.getBroadcastId(); + broadcastId = (0x00FF0000 & (broadcast_id[2] << 16)); + broadcastId |= (0x0000FF00 & (broadcast_id[1] << 8)); + broadcastId |= (0x000000FF & broadcast_id[0]); + mService.updatePAResultsMap(srcInfo.getSourceDevice(), colocatedAddressType, + mBAService.BroadcatGetAdvHandle(), + mBAService.BroadcatGetAdvHandle(), + mBAService.BroadcastGetAdvInterval(), + broadcastId); + } + res = mService.getPAResults(srcInfo.getSourceDevice()); + } + if (res != null) { + int syncHandle = res.mSyncHandle; + log("processPASyncState: syncHandle" + res.mSyncHandle); + if (mNoPast == false && syncHandle != BCService.INVALID_SYNC_HANDLE) { + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { + log("Collocated Case Initiate PAST for :" + mDevice + "syncHandle:" + syncHandle + + "serviceData" + serviceData); + serviceData = 0x000000FF & srcInfo.getSourceId(); + serviceData = serviceData << 8; + //advA matches EXT_ADV_ADDRESS + //but not matches source address (as we would have written pAddr) + serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); + serviceData = serviceData | (BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); + try { + mPeriodicAdvManager.transferSetInfo(mDevice, serviceData, syncHandle,mPeriodicAdvCallback); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "transferSetInfo: IllegalArgumentException : PAST failure"); + //ignore + } + } else { + serviceData = 0x000000FF & srcInfo.getSourceId(); + serviceData = serviceData << 8; + //advA matches EXT_ADV_ADDRESS + //also matches source address (as we would have written) + serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); + serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); + log("Initiate PAST for :" + mDevice + "syncHandle:" + syncHandle + + "serviceData" + serviceData); + mPeriodicAdvManager.transferSync(mDevice, serviceData, syncHandle); + } + } + } else { + Log.e(TAG, "There is no valid sync handle for this Source"); + if (mAutoAssist) { + //initiate Auto Assist procedure for this device + mService.getBassUtils().triggerAutoAssist (srcInfo); + } + } + } + else if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED || + srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST) { + //Cancel the existing sync and Invalidate the sync handle + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice()) == false) { + if (mSyncingOnBehalfOfGroup == false) { + //Cancel the Sync only If it is NOT syced on behalf of group. + //group based sync will be kept active PSYNC_ACTIVE_TIMEOUT seconds so that + //all group members can get back in sync + log("Unregister sync as It is non colocated"); + cancelActiveSync(srcInfo.getSourceDevice()); + } + } else { + //trigger scan stop here + mService.stopScanOffloadInternal(mDevice, false); + } + } + } + + /*Actual OTA advertising address based check + Use this after the addition of Broadcast source*/ + private boolean isAddedBroadcastSourceIsLocal (BluetoothDevice device) + { + if (device == null) { + Log.e(TAG, "device handle is null"); + return false; + } + String localBroadcasterAddr = null; + ///*_BMS + if (mPublicAddrForcSrc) { + localBroadcasterAddr = BluetoothAdapter.getDefaultAdapter().getAddress(); + } else { + if (mBAService == null) { + mBAService = BroadcastService.getBroadcastService(); + } + if (mBAService == null || mBAService.isBroadcastActive() != true) { + Log.e(TAG, "isAddedBroadcastSourceIsLocal: colocated source handle is unavailable"); + return false; + } + localBroadcasterAddr = mBAService.BroadcastGetAdvAddress(); + } + //_BMS*/ + boolean ret = false; + if (localBroadcasterAddr == null) { + Log.e(TAG, "isAddedBroadcastSourceIsLocal: localBroadcasterAddr is null"); + ret = false; + } else { + ret = localBroadcasterAddr.equals(device.getAddress()); + } + log("isAddedBroadcastSourceIsLocal returns" +ret); + return ret; + } + + private void checkAndUpdateBroadcastCode(BleBroadcastSourceInfo srcInfo) { + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { + if (mForceSB == true || + srcInfo.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { + //query the Encryption Key from BMS and update + ///*_BMS + byte[] colocatedBcastCode = mBAService.GetEncryptionKey(null); + if (mBAService.isBroadcastStreamingEncrypted() == false) { + Log.e(TAG, "seem to be Unencrypted colocated broadcast"); + //do nothing + return; + } + log("colocatedBcastCode is " + colocatedBcastCode); + //queue a fresh command to update the + Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE); + m.obj = srcInfo; + m.arg1 = FRESH; + log("checkAndUpdateBroadcastCode: src device: " + srcInfo.getSourceDevice()); + sendMessage(m); + //_BMS*/ + } + } else { + log("checkAndUpdateBroadcastCode"); + //non colocated case, Broadcast PIN should have been updated from lyaer + //If there is pending one process it Now + if (srcInfo.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED && + mSetBroadcastCodePending == true) { + //Make a QUEUED command + log("Update the Broadcast now"); + Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE); + m.obj = mSetBroadcastPINSrcInfo; + m.arg1 = QUEUED; + + sendMessage(m); + mSetBroadcastCodePending = false; + mSetBroadcastPINSrcInfo = null; + } + } + } + + private List getListOfBisIndicies(int bisIndicies, int subGroupId, byte[] metaData) { + List bcastIndicies = new ArrayList(); + int index =0; + log("getListOfBisIndicies:" + bisIndicies); + while (bisIndicies != 0) { + if ((bisIndicies & 0x00000001) == 0x00000001) { + BleBroadcastSourceChannel bI = + new BleBroadcastSourceChannel(index, Integer.toString(index), true, subGroupId, metaData); + bcastIndicies.add(bI); + log("Adding BIS index for :" + index); + } + bisIndicies = bisIndicies>>1; + index++; + } + return bcastIndicies; + + } + + private void processBroadcastReceiverState (byte[] receiverState, BluetoothGattCharacteristic characteristic) { + int index = -1; + boolean isEmptyEntry = false; + BleBroadcastSourceInfo srcInfo = null; + + log("processBroadcastReceiverState:: characteristic:" + characteristic); + + byte sourceId = 0; + if (receiverState.length > 0) + sourceId = receiverState[BCAST_RCVR_STATE_SRC_ID_IDX]; + log("processBroadcastReceiverState: receiverState length: " + receiverState.length); + if (receiverState.length == 0 || + isEmpty(Arrays.copyOfRange(receiverState, 1, receiverState.length-1))) { + log("This is an Empty Entry"); + if (mPendingOperation == REMOVE_BCAST_SOURCE) { + srcInfo = new BleBroadcastSourceInfo(mPendingSourceId); + } else if (receiverState.length == 0) { + if (mBleBroadcastSourceInfos != null) { + mTempSourceId = (byte)mBleBroadcastSourceInfos.size(); + } + if (mTempSourceId < NUM_OF_BROADCAST_RECEIVER_STATES) { + mTempSourceId++; + srcInfo = new BleBroadcastSourceInfo(mTempSourceId); + } else { + Log.e(TAG, "reached the remote supported max SourceInfos"); + return; + } + } + isEmptyEntry = true; + //just create an Empty entry + if (srcInfo.isEmptyEntry()) { + log("An empty entry has been created"); + } + } + else { + byte sourceAddressType = receiverState[BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX]; + byte[] sourceAddress = new byte[BCAST_RCVR_STATE_SRC_ADDR_SIZE]; + System.arraycopy(receiverState, BCAST_RCVR_STATE_SRC_ADDR_START_IDX, sourceAddress, 0, BCAST_RCVR_STATE_SRC_ADDR_SIZE); + byte sourceAdvSid = receiverState[BCAST_RCVR_STATE_SRC_ADV_SID_IDX]; + + byte[] broadcastIdBytes = new byte[BROADCAST_SOURCE_ID_LENGTH]; + System.arraycopy(receiverState, BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX, broadcastIdBytes, 0, BROADCAST_SOURCE_ID_LENGTH); + int broadcastId = (0x00FF0000 & (broadcastIdBytes[2] << 16)); + broadcastId |= (0x0000FF00 & (broadcastIdBytes[1] << 8)); + broadcastId |= (0x000000FF & broadcastIdBytes[0]); + + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = btAdapter.getRemoteDevice(reverseBytes(sourceAddress)); + byte metaDataSyncState = receiverState[BCAST_RCVR_STATE_PA_SYNC_IDX]; + + + byte encyptionStatus = receiverState[BCAST_RCVR_STATE_ENC_STATUS_IDX]; + byte[] badBroadcastCode = null; + byte badBroadcastCodeLen = 0; + byte numSubGroups = 0; + byte[] metadataLength = null; + byte[] metaData = null; + if (encyptionStatus == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_BADCODE) { + badBroadcastCode = new byte[BCAST_RCVR_STATE_BADCODE_SIZE]; + System.arraycopy(receiverState, BCAST_RCVR_STATE_BADCODE_START_IDX, badBroadcastCode, 0, BCAST_RCVR_STATE_BADCODE_SIZE); + badBroadcastCode = reverseBytes(badBroadcastCode); + badBroadcastCodeLen = BCAST_RCVR_STATE_BADCODE_SIZE; + } + numSubGroups = receiverState[BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen]; + int offset = BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen + 1; + //Map of Bis Status + Map> bisIndexList = new HashMap>(); + //Map for Metada + Map metadataList = new HashMap(); + metadataLength = new byte[numSubGroups]; + byte audioSyncState = 0; + for (int i = 0; i < numSubGroups; i++) { + byte[] audioSyncIndex = new byte[BCAST_RCVR_STATE_BIS_SYNC_SIZE]; + System.arraycopy(receiverState, offset, audioSyncIndex, 0, BCAST_RCVR_STATE_BIS_SYNC_SIZE); + offset = offset + BCAST_RCVR_STATE_BIS_SYNC_SIZE; + log("BIS index byte array: "); + BassUtils.printByteArray(audioSyncIndex); + ByteBuffer wrapped = ByteBuffer.wrap(reverseBytes(audioSyncIndex)); + int audioBisIndex = wrapped.getInt(); + if (audioBisIndex == 0xFFFFFFFF) { + log("Remote failed to sync to BIS"); + audioSyncState = 0x00; + audioBisIndex = 0; + } else { + //Bits (0-30)=> (1-31) + audioBisIndex = audioBisIndex << 1; + log("BIS index converted: " + audioBisIndex); + if (audioBisIndex != 0){ + //If any BIS is in sync, Set Audio state as ON + audioSyncState = 0x01; + } + } + + metadataLength[i] = receiverState[offset++]; + if (metadataLength[i] != 0) { + log("metadata of length: " + metadataLength[i] + "is avaialble"); + metaData = new byte[metadataLength[i]]; + System.arraycopy(receiverState, offset, metaData, 0, metadataLength[i]); + offset = offset + metadataLength[i]; + metaData = reverseBytes(metaData); + metadataList.put(i, metaData); + } + bisIndexList.put(i, getListOfBisIndicies(audioBisIndex, i, metaData)); + } + srcInfo = new BleBroadcastSourceInfo(device, + sourceId, + sourceAdvSid, + broadcastId, + (int)sourceAddressType, + (int)metaDataSyncState, + (int)encyptionStatus, + badBroadcastCode, + numSubGroups, + (int)audioSyncState, + bisIndexList, + metadataList + ); + } + BleBroadcastSourceInfo oldSourceInfo = mBleBroadcastSourceInfos.get(characteristic.getInstanceId()); + if (oldSourceInfo == null) { + log("Initial Read and Populating values"); + if (mBleBroadcastSourceInfos.size() == NUM_OF_BROADCAST_RECEIVER_STATES) { + Log.e(TAG, "reached the Max SourceInfos"); + return; + } + mBleBroadcastSourceInfos.put(characteristic.getInstanceId(), srcInfo); + checkAndUpdateBroadcastCode(srcInfo); + processPASyncState(srcInfo); + } else { + log("old sourceInfo: " + oldSourceInfo); + log("new sourceInfo: " + srcInfo); + mBleBroadcastSourceInfos.replace(characteristic.getInstanceId(), srcInfo); + if (oldSourceInfo.isEmptyEntry() == true) { + log("New Source Addition"); + sendPendingCallbacks(ADD_BCAST_SOURCE, + srcInfo.getSourceId(), BluetoothGatt.GATT_SUCCESS); + checkAndUpdateBroadcastCode(srcInfo); + processPASyncState(srcInfo); + } else { + if (isEmptyEntry) { + BluetoothDevice removedDevice = oldSourceInfo.getSourceDevice(); + log("sourceInfo removal" + removedDevice); + cancelActiveSync(removedDevice); + sendPendingCallbacks(REMOVE_BCAST_SOURCE, + srcInfo.getSourceId(), BluetoothGatt.GATT_SUCCESS); + } else { + log("update to an existing srcInfo"); + sendPendingCallbacks(UPDATE_BCAST_SOURCE, + srcInfo.getSourceId(),BluetoothGatt.GATT_SUCCESS); + processPASyncState(srcInfo); + checkAndUpdateBroadcastCode(srcInfo); + } + } + } + index = srcInfo.getSourceId(); + if (index == -1) { + log("processBroadcastReceiverState: invalid index"); + return; + } + broadcastReceiverState(srcInfo, index, NUM_OF_BROADCAST_RECEIVER_STATES); + } + // Implements callback methods for GATT events that the app cares about. For example, + // connection change and services discovered. + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + boolean isStateChanged = false; + log( "onConnectionStateChange : Status=" + status + "newState" + newState); + if (newState == BluetoothProfile.STATE_CONNECTED && getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + isStateChanged = true; + Log.w(TAG, "Bassclient Connected from Disconnected state: " + mDevice); + if (mService.okToConnect(mDevice)) { + log("Bassclient Connected to: " + mDevice); + if (mBluetoothGatt != null) { + log( "Attempting to start service discovery:" + + mBluetoothGatt.discoverServices()); + mDiscoveryInitiated = true; + } + } else { + if (mBluetoothGatt != null) { + // Reject the connection + Log.w(TAG, "Bassclient Connect request rejected: " + mDevice); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + //force move to disconnected + newState = BluetoothProfile.STATE_DISCONNECTED; + } + } + } else if (newState == BluetoothProfile.STATE_DISCONNECTED && + getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + isStateChanged = true; + log( "Disconnected from Bass GATT server."); + } + if (isStateChanged) { + Message m = obtainMessage(CONNECTION_STATE_CHANGED); + m.obj = newState; + sendMessage(m); + } + } + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + log("onServicesDiscovered:" + status); + if (mDiscoveryInitiated == true) { + mDiscoveryInitiated = false; + if (status == BluetoothGatt.GATT_SUCCESS && mBluetoothGatt != null) { + mBluetoothGatt.requestMtu(BASS_MAX_BYTES); + mMTUChangeRequested = true; + } else { + Log.w(TAG, "onServicesDiscovered received: " + status + + "mBluetoothGatt" + mBluetoothGatt); + } + } else { + log("remote initiated callback"); + //do nothing + } + } + @Override + public void onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + int status) { + log( "onCharacteristicRead:: status: " + status + "char:" + characteristic); + + if (status == BluetoothGatt.GATT_SUCCESS && + characteristic.getUuid().equals(BASS_BCAST_RECEIVER_STATE)) { + log( "onCharacteristicRead: BASS_BCAST_RECEIVER_STATE: status" + status); + logByteArray("Received ", characteristic.getValue(), 0, + characteristic.getValue().length); + if (characteristic.getValue() == null) { + Log.e(TAG, "Remote receiver state is NULL"); + return; + } + processBroadcastReceiverState(characteristic.getValue(), characteristic); + } + // switch to receiving notifications after initial characteristic read + BluetoothGattDescriptor desc = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); + if (mBluetoothGatt != null && desc != null) { + log("Setting the value for Desc"); + mBluetoothGatt.setCharacteristicNotification(characteristic, true); + desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + mBluetoothGatt.writeDescriptor(desc); + } else { + Log.w(TAG, "CCC for " + characteristic + "seem to be not present"); + //atleast move the SM to stable state + Message m = obtainMessage(GATT_TXN_PROCESSED); + m.arg1 = status; + sendMessage(m); + } + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, + int status) { + log("onDescriptorWrite"); + if (status == BluetoothGatt.GATT_SUCCESS && + descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIG)) { + log("CCC write resp"); + } + + //Move the SM to connected so further reads happens + Message m = obtainMessage(GATT_TXN_PROCESSED); + m.arg1 = status; + sendMessage(m); + } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) + { + log("onMtuChanged: mtu:" + mtu); + if (mMTUChangeRequested == true && mBluetoothGatt != null) { + acquireAllBassChars(); + mMTUChangeRequested = false; + } else { + log("onMtuChanged is remote initiated trigger, mBluetoothGatt:" + mBluetoothGatt); + //Do nothing + } + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + log( "onCharacteristicChanged :: " + + characteristic.getUuid().toString()); + if (characteristic.getUuid().equals(BASS_BCAST_RECEIVER_STATE)) { + log( "onCharacteristicChanged is rcvr State :: " + + characteristic.getUuid().toString()); + if (characteristic.getValue() == null) { + Log.e(TAG, "Remote receiver state is NULL"); + return; + } + logByteArray("onCharacteristicChanged: Received ", characteristic.getValue(), 0, + characteristic.getValue().length); + processBroadcastReceiverState(characteristic.getValue(), characteristic); + } + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + log( "onCharacteristicWrite: " + + characteristic.getUuid().toString() + + "status:" + status); + if (status == 0 && + characteristic.getUuid().equals(BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) { + log( "BASS_BCAST_AUDIO_SCAN_CTRL_POINT is written successfully"); + } + Message m = obtainMessage(GATT_TXN_PROCESSED); + m.arg1 = status; + sendMessage(m); + } + }; + + public List getAllBroadcastSourceInformation() { + log( "getAllBroadcastSourceInformation"); + List list = new ArrayList(mBleBroadcastSourceInfos.values()); + return list; + } + + void acquireAllBassChars() { + clearCharsCache(); + BluetoothGattService service = null; + if (mBluetoothGatt != null) { + log("getting Bass Service handle"); + service = mBluetoothGatt.getService(BASS_UUID); + } + if (service != null) { + log( "found BASS_SERVICE"); + List allChars = service.getCharacteristics(); + int numOfChars = allChars.size(); + NUM_OF_BROADCAST_RECEIVER_STATES = numOfChars-1; + log( "Total number of chars" + numOfChars); + //static var to keep track of read callbacks + num_of_recever_states = NUM_OF_BROADCAST_RECEIVER_STATES; + for (int i = 0; i < allChars.size(); i++) { + if (allChars.get(i).getUuid().equals(BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) { + mBroadcastScanControlPoint = allChars.get(i); + log( "Index of ScanCtrlPoint:" + i); + } else { + log( "Reading " + i + "th ReceiverState"); + mBroadcastReceiverStates.add(allChars.get(i)); + Message m = obtainMessage(READ_BASS_CHARACTERISTICS); + m.obj = allChars.get(i); + sendMessage(m); + } + } + } else { + Log.e(TAG, "acquireAllBassChars: BASS service not found"); + } + } + + void clearCharsCache() { + if (mBroadcastReceiverStates != null) { + mBroadcastReceiverStates.clear(); + } + if (mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint = null; + } + num_of_recever_states = 0; + if (mBleBroadcastSourceInfos != null) { + mBleBroadcastSourceInfos.clear(); + } + mPendingOperation = -1; + } + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + log( "Enter Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + clearCharsCache(); + mTempSourceId = 0; + removeDeferredMessages(DISCONNECT); + + if (mLastConnectionState == -1) { + log( "no Broadcast of initial profile state "); + } else { + if (mPacsAvail == true) { + /*_PACS + PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); + if (mPacsClientService != null) { + log("trigger disconnect to Pacs"); + mPacsClientService.disconnect(mDevice); + } else { + Log.e(TAG, "PACs interface is null"); + } + _PACS*/ + } + + ///*_VCP + if (mVcpForBroadcast) { + VcpController vcpController = VcpController.getVcpController(); + if (vcpController != null) { + log("trigger disconnect to Vcp Renderer"); + if (!vcpController.disconnect(mDevice, BluetoothVcp.MODE_BROADCAST)) { + log("Disconnect Vcp failed"); + } + } else { + Log.e(TAG, "VcpController interface is null"); + } + } + //_VCP*/ + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_DISCONNECTED); + } + } + + @Override + public void exit() { + log("Exit Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + log("Connecting to " + mDevice); + if (mBluetoothGatt != null) { + Log.d(TAG, "clear off, pending wl connection"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + + if ((mBluetoothGatt = mDevice.connectGatt(mService, mIsWhitelist, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true)) == null) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } else { + transitionTo(mConnecting); + } + break; + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + log("remote/wl connection, ensure csip is up as well"); + if (mNoCSIPReconn == false && mService != null && + mService.isLockSupportAvailable(mDevice)) { + /////*_CSIP + mCsipConnected = false; + mSetCoordinator.connect(mService.mCsipAppId, mDevice); + transitionTo(mConnecting); + break; + ////_CSIP*/ + } else { + transitionTo(mConnected); + } + } else { + Log.w(TAG, "Disconected: Connection failed to " + mDevice); + } + break; + case PSYNC_ACTIVE_TIMEOUT: + cancelActiveSync(null); + break; + default: + log("DISCONNECTED: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + log( "Enter Connecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_CONNECTING); + } + + @Override + public void exit() { + log("Exit Connecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + log("Already Connecting to " + mDevice); + log("Ignore this connection request " + mDevice); + break; + case DISCONNECT: + Log.w(TAG, "Connecting: DISCONNECT deferred: " + mDevice); + deferMessage(message); + break; + case READ_BASS_CHARACTERISTICS: + Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice); + deferMessage(message); + break; + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_CONNECTED) { + ///*_CSIP + if (mCsipConnected == true) { + Log.e(TAG, "CSIP is already up, ignore this DUP event"); + break; + } + mCsipConnected = true; + Log.d(TAG, "Csip connected"); + transitionTo(mConnected); + } else { + Log.w(TAG, "CSIP Connection failed to " + mDevice); + if (mBluetoothGatt != null) { + //disc bass + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + transitionTo(mDisconnected); + } + + break; + case CONNECTION_STATE_CHANGED: + state = (int)message.obj; + Log.w(TAG, "Connecting: connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + if (mService != null && + mService.isLockSupportAvailable(mDevice)) { + ///*_CSIP + //If Lock support available & connect to csip + mCsipConnected = false; + mSetCoordinator.connect(mService.mCsipAppId, mDevice); + break; + //_CSIP*/ + } else { + transitionTo(mConnected); + } + } else { + Log.w(TAG, "Connection failed to " + mDevice); + transitionTo(mDisconnected); + } + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "CONNECT_TIMEOUT"); + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + transitionTo(mDisconnected); + break; + case PSYNC_ACTIVE_TIMEOUT: + deferMessage(message); + break; + default: + log("CONNECTING: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + private byte[] reverseBytes(byte[] a) { + if (mNoReverse) { + log("no reverse is enabled>"); + return a; + } + for(int i=0; i bisIndicies, int subGroupId) { + int audioBisIndex = 0; + if (bisIndicies != null) { + for (int i=0; i>> 8); + res[11] = (byte)((paRes.mBroadcastId & 0x0000000000FF0000) >>> 16); + if (mDefNoPAS == false && + srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + res[12] = (byte)(0x01); + } else { + log("setting PA sync to ZERO"); + res[12] = (byte)0x00; + } + + res[13] = (byte)(paRes.mPAInterval & 0x00000000000000FF); + res[14] = (byte)((paRes.mPAInterval & 0x000000000000FF00)>>>8); + + res[15] = base_.getNumberOfSubgroupsofBIG(); + + int offset = 16; + for (int i=0; i>>8); + res[offset++] = (byte)((bisIndexValue & 0x0000000000FF0000)>>>16); + res[offset++] = (byte)((bisIndexValue & 0x00000000FF000000)>>>24); + + res[offset++] = metaDataLength[i]; + if (metaDataLength[i] != 0) { + if (isLocalBroadcastSource(broadcastSource) == false) { + byte[] revMetadata = reverseBytes(base_.getMetadata(i)); + System.arraycopy(revMetadata, 0, res, offset, metaDataLength[i]); + } else { + System.arraycopy(base_.getMetadata(i), 0, res, offset, metaDataLength[i]); + } + } + offset = offset + metaDataLength[i]; + } + + log("ADD_BCAST_SOURCE in Bytes"); + BassUtils.printByteArray(res); + return res; + } + + private byte[] convertSourceInfoToUpdateSourceByteArray(BleBroadcastSourceInfo srcInfo) { + byte[] res; + int updateSourceFixedLength = 6; + BCService.PAResults paRes = null; + BleBroadcastSourceInfo existingSI = getBroadcastSourceInfoForSourceId(srcInfo.getSourceId()); + if (existingSI == null) { + log("no existing SI for update source op"); + return null; + } + + byte numSubGroups = existingSI.getNumberOfSubGroups(); + //on Modify source, dont update any Metadata + byte metaDataLength = 0; + res = new byte [updateSourceFixedLength + numSubGroups*5]; + + res[0] = BASS_UPDATE_SOURCE_OPCODE; + res[1] = srcInfo.getSourceId(); + + if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + res[2] = (byte)(0x01); + } else { + res[2] = (byte)0x00; + } + //update these from existing SI + BluetoothDevice existingSrcDevice = existingSI.getSourceDevice(); + if (isAddedBroadcastSourceIsLocal(existingSrcDevice)) { + int paInterval = 0x0000FFFF; + paInterval = mBAService.BroadcastGetAdvInterval(); + res[4] = (byte)((paInterval & 0x000000000000FF00)>>>8); + res[3] = (byte)(paInterval & 0x00000000000000FF); + } else { + //for non-c mmodify op, set PA Interval as UNKNOWN + res[4] = (byte)0xFF; + res[3] = (byte)0xFF; + } + //For modify op, just set number of Subgroups as UNKNOWN + //ZERO is treated as UNKNOWN + res[5] = numSubGroups; + + int offset = 6; + int bisIndexValue = 0; + Map bisIndexList = existingSI.getBisIndexList(); + if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { + //Force BIS index value to NO_PREF for modify SRC + bisIndexValue = 0xFFFFFFFF; + } else { + bisIndexValue = 0x00000000; + } + log("UPDATE_BCAST_SOURCE b4: bisIndexValue : " + bisIndexValue); + //If there is an empty List, set NO pref all subgroups + if (bisIndexList == null || bisIndexList.size() == 0) { + bisIndexValue = 0xFFFFFFFF; + } + for (int i=0; i>>8); + res[offset++] = (byte)((bisIndexValue & 0x0000000000FF0000)>>>16); + res[offset++] = (byte)((bisIndexValue & 0x00000000FF000000)>>>24); + + res[offset++] = metaDataLength; + } + log("UPDATE_BCAST_SOURCE in Bytes"); + BassUtils.printByteArray(res); + return res; + } + + private byte[] convertAsciitoValues (byte[] val) { + byte[] ret = new byte[val.length]; + for (int i=0; i< val.length; i++) { + ret[i] = (byte)(val[i] - (byte)'0'); + } + log("convertAsciitoValues: returns:" + Arrays.toString(val)); + return ret; + } + + private byte[] convertSourceInfoToSetBroadcastCodeByteArray(BleBroadcastSourceInfo srcInfo) { + + byte[] res = new byte[PIN_CODE_CMD_LEN]; + res[0] = BASS_SET_BCAST_PIN_OPCODE; + res[1] = srcInfo.getSourceId(); + log("convertSourceInfoToSetBroadcastCodeByteArray: Source device : " + srcInfo.getSourceDevice()); + byte[] actualPIN = null; + //srcInfo.getSourceDevice() will be NULL if this request coming from SDK + // srcInfo.getSourceDevice() will have valid Source device only If this is + //collocated device + if (srcInfo.getSourceDevice() != null && + isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { + //colocated Source addition + //query the Encryption Key from BMS and update + ///*_BMS + actualPIN = mBAService.GetEncryptionKey(null); + //_BMS*/ + log("colocatedBcastCode is " + Arrays.toString(actualPIN)); + } else { + //Can Keep as ASCII as is + String reversePIN = new StringBuffer(srcInfo.getBroadcastCode()).reverse().toString(); + actualPIN = reversePIN.getBytes(); + } + if (actualPIN == null) { + Log.e(TAG, "actual PIN is null"); + return null; + } else { + log( "byte array broadcast Code:" + Arrays.toString(actualPIN)); + log( "pinLength:" + actualPIN.length); + + //Fill the PIN code in the Last Position + System.arraycopy(actualPIN, 0, res, ((PIN_CODE_CMD_LEN)-actualPIN.length), actualPIN.length); + + log("SET_BCAST_PIN in Bytes"); + BassUtils.printByteArray(res); + } + return res; + } + + private boolean IsItRightTimeToUpdateBroadcastPIN(byte srcId) { + Collection srcInfos = mBleBroadcastSourceInfos.values(); + Iterator iterator = srcInfos.iterator(); + boolean ret = false; + if (mForceSB) { + log("force SB is set"); + return true; + } + while (iterator.hasNext()) { + BleBroadcastSourceInfo sI = iterator.next(); + if (sI == null) { + log("src Info is null"); + continue; + } + if (srcId == sI.getSourceId() && + sI.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { + ret = true; + break; + } + } + log("IsItRightTimeToUpdateBroadcastPIN returning:" + ret); + return ret; + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + log( "Enter Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + + removeDeferredMessages(CONNECT); + if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { + log("CONNECTED->CONNTECTED: Ignore"); + } else { + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_CONNECTED); + //initialize PACs for this devices + if (mPacsAvail == true) { + /* + PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); + if (mPacsClientService != null) { + log("trigger connect to Pacs"); + mPacsClientService.connect(mDevice); + } else { + Log.e(TAG, "PACs interface is null"); + } + */ + } + + ///*_VCP + if (mVcpForBroadcast) { + VcpController vcpController = VcpController.getVcpController(); + if (vcpController != null) { + log("trigger connect to Vcp Renderer"); + if (!vcpController.connect(mDevice, BluetoothVcp.MODE_BROADCAST)) { + log("Connect vcp failed"); + } + } else { + Log.e(TAG, "VcpController interface is null"); + } + } + //_VCP*/ + } + } + + @Override + public void exit() { + log("Exit Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case CONNECT: + Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); + break; + case DISCONNECT: + log("Disconnecting from " + mDevice); + if (mBluetoothGatt != null) { + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + //transitionTo(mDisconnecting); + cancelActiveSync(null); + //Trigger the CSip disconnection, dont worry about pass/failure + if (mCsipConnected && mSetCoordinator != null) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + transitionTo(mDisconnected); + } else { + log("mBluetoothGatt is null"); + } + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "Connected:connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "device is already connected to Bass" + mDevice); + } else { + Log.w(TAG, "unexpected disconnected from " + mDevice); + cancelActiveSync(null); + ///*_CSIP + //Trigger the CSip disconnection, dont worry about pass/failure + if (mCsipConnected) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + //_CSIP*/ + transitionTo(mDisconnected); + } + break; + case READ_BASS_CHARACTERISTICS: + BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic)message.obj; + if (mBluetoothGatt != null) { + mBluetoothGatt.readCharacteristic(characteristic); + transitionTo(mConnectedProcessing); + } else { + Log.e(TAG, "READ_BASS_CHARACTERISTICS is ignored, Gatt handle is null"); + } + break; + case START_SCAN_OFFLOAD: + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(REMOTE_SCAN_START); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + transitionTo(mConnectedProcessing); + } else { + log("no Bluetooth Gatt handle, may need to fetch write"); + } + break; + case STOP_SCAN_OFFLOAD: + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(REMOTE_SCAN_STOP); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + transitionTo(mConnectedProcessing); + } else { + log("no Bluetooth Gatt handle, may need to fetch write"); + } + break; + case SELECT_BCAST_SOURCE: + ScanResult scanRes = (ScanResult)message.obj; + boolean auto = ((int) message.arg1) == AUTO; + boolean isGroupOp = ((int) message.arg2) == GROUP_OP; + selectBroadcastSource(scanRes, isGroupOp, auto); + break; + case ADD_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + log("Adding Broadcast source" + srcInfo); + byte[] addSourceInfo = convertSourceInfoToAddSourceByteArray(srcInfo); + if (addSourceInfo == null) { + Log.e(TAG, "add source: source Info is NULL"); + break; + } + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(addSourceInfo); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "ADD_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + } + break; + case UPDATE_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + mAutoTriggerred = ((int) message.arg1) == AUTO; + log("Updating Broadcast source" + srcInfo); + byte[] updateSourceInfo = convertSourceInfoToUpdateSourceByteArray(srcInfo); + if (updateSourceInfo == null) { + Log.e(TAG, "update source: source Info is NULL"); + break; + } + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(updateSourceInfo); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + mPendingSourceId = srcInfo.getSourceId(); + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(UPDATE_BCAST_SOURCE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + } + break; + case SET_BCAST_CODE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + int cmdType = message.arg1; + log("SET_BCAST_CODE srcInfo: " + srcInfo); + + if (cmdType != QUEUED && + IsItRightTimeToUpdateBroadcastPIN(srcInfo.getSourceId()) != true) { + mSetBroadcastCodePending = true; + mSetBroadcastPINSrcInfo = srcInfo; + log("Ignore SET_BCAST now, but store it for later"); + //notify so that lock release happens as SET_BCAST_CODE + //queued for future + mService.notifyOperationCompletion(mDevice,SET_BCAST_CODE); + } else { + byte[] setBroadcastPINcmd = convertSourceInfoToSetBroadcastCodeByteArray(srcInfo); + if (setBroadcastPINcmd == null) { + Log.e(TAG, "SET_BCAST_CODE: Broadcast code is NULL"); + break; + } + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(setBroadcastPINcmd); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + mPendingSourceId = srcInfo.getSourceId(); + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "SET_BCAST_CODE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(SET_BCAST_CODE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + + } + } + break; + case REMOVE_BCAST_SOURCE: + byte sourceId = (byte)message.arg1; + BluetoothDevice audioSrc = (BluetoothDevice)message.obj; + log("Removing Broadcast source: audioSource:" + audioSrc + "sourceId:" + sourceId); + byte[] removeSourceInfo = new byte [2]; + removeSourceInfo[0] = BASS_REMOVE_SOURCE_OPCODE; + removeSourceInfo[1] = sourceId; + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(removeSourceInfo); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + mPendingSourceId = sourceId; + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "REMOVE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(REMOVE_BCAST_SOURCE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + + } + break; + case PSYNC_ACTIVE_TIMEOUT: + cancelActiveSync(null); + break; + default: + log("CONNECTED: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + void sendPendingCallbacks(int pendingOp, byte sourceId, int status) { + if (status != 0) { + //Notify service only In case of failure cases + //Success case would have been notified through State machine anyways + mService.notifyOperationCompletion(mDevice, pendingOp); + } + switch (pendingOp) { + case START_SCAN_OFFLOAD: + if (status != 0) { + if (mAutoTriggerred == false) { + log("notify the app only if start Scan offload fails"); + //shouldnt happen in general + mService.sendBroadcastSourceSelectedCallback(mDevice, null, status); + cancelActiveSync(null); + } else { + mAutoTriggerred = false; + } + } + break; + case ADD_BCAST_SOURCE: + if (status != 0) { + sourceId = INVALID_SRC_ID; + cancelActiveSync(null); + //stop Scan offload for colocated case + mService.stopScanOffloadInternal(mDevice, false); + } + mService.sendAddBroadcastSourceCallback(mDevice, sourceId, status); + break; + case UPDATE_BCAST_SOURCE: + if (mAutoTriggerred == false) { + mService.sendUpdateBroadcastSourceCallback(mDevice, sourceId, status); + } else { + mAutoTriggerred = false; + } + break; + case REMOVE_BCAST_SOURCE: + mService.sendRemoveBroadcastSourceCallback(mDevice, sourceId, status); + break; + case SET_BCAST_CODE: + mService.sendSetBroadcastPINupdatedCallback(mDevice, sourceId, status); + break; + default: + { + log("sendPendingCallbacks: unhandled case"); + } + } + } + @VisibleForTesting + class ConnectedProcessing extends State { + @Override + public void enter() { + log( "Enter ConnectedProcessing(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public void exit() { + log("Exit ConnectedProcessing(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + } + @Override + public boolean processMessage(Message message) { + log("ConnectedProcessing process message(" + mDevice + "): " + messageWhatToString( + message.what)); + switch (message.what) { + case CONNECT: + Log.w(TAG, "CONNECT request is ignored" + mDevice); + break; + case DISCONNECT: + Log.w(TAG, "DISCONNECT requested!: " + mDevice); + if (mBluetoothGatt != null) { + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + cancelActiveSync(null); + //Trigger the CSIP disconnection, dont worry about pass/failure + if (mCsipConnected && mSetCoordinator != null) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + transitionTo(mDisconnected); + } else { + log("mBluetoothGatt is null"); + } + break; + case READ_BASS_CHARACTERISTICS: + Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice); + deferMessage(message); + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "ConnectedProcessing: connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "should never happen from this state"); + } else { + Log.w(TAG, "Unexpected disconnection " + mDevice); + transitionTo(mDisconnected); + } + break; + case GATT_TXN_PROCESSED: + removeMessages(GATT_TXN_TIMEOUT); + int status = (int)message.arg1; + log( "GATT transaction processed for" + mDevice); + mService.notifyOperationCompletion(mDevice, mPendingOperation); + if (status == BluetoothGatt.GATT_SUCCESS) { + if (mPendingOperation == SET_BCAST_CODE) { + //If Pending operation is SET_BCAST_CODE + //send callback to notify BCAST is updated + //This is needed only for SET_BCAST operation + sendPendingCallbacks(mPendingOperation, + mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); + } + } else { + //any failure to write operation + //will be converted to corresponding + //callback with failure status + sendPendingCallbacks(mPendingOperation, + mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_FAILURE); + } + transitionTo(mConnected); + break; + case GATT_TXN_TIMEOUT: + log( "GATT transaction timedout for" + mDevice); + mService.notifyOperationCompletion(mDevice, mPendingOperation); + sendPendingCallbacks(mPendingOperation, + mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_TXN_TIMEOUT); + mPendingOperation = -1; + transitionTo(mConnected); + mPendingSourceId = -1; + break; + case START_SCAN_OFFLOAD: + case STOP_SCAN_OFFLOAD: + case SELECT_BCAST_SOURCE: + case ADD_BCAST_SOURCE: + case SET_BCAST_CODE: + case REMOVE_BCAST_SOURCE: + case PSYNC_ACTIVE_TIMEOUT: + log("defer the message:" + message.what + "so that it will be processed later"); + deferMessage(message); + break; + default: + log("CONNECTEDPROCESSING: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + log( "Enter Disconnecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_DISCONNECTING); + } + @Override + public void exit() { + log("Exit Disconnecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + removeMessages(CONNECT_TIMEOUT); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + } + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + messageWhatToString( + message.what)); + switch (message.what) { + case CONNECT: + log("Disconnecting to " + mDevice); + log("deferring this connection request " + mDevice); + deferMessage(message); + break; + case DISCONNECT: + Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice); + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "Disconnecting: connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.e(TAG, "should never happen from this state"); + transitionTo(mConnected); + } else { + Log.w(TAG, "disconnection successfull to " + mDevice); + cancelActiveSync(null); + transitionTo(mDisconnected); + ///*_CSIP + //Trigger the CSip disconnection, dont worry about pass/failure + if (mCsipConnected) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + //_CSIP*/ + } + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "CONNECT_TIMEOUT"); + + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + transitionTo(mDisconnected); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + + void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) { + log( "broadcastConnectionState " + device + ": " + fromState + "->" + toState); + if (fromState == BluetoothProfile.STATE_CONNECTED && + toState == BluetoothProfile.STATE_CONNECTED) { + log("CONNECTED->CONNTECTED: Ignore"); + return; + } + Intent intent = new Intent(BluetoothSyncHelper.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mService.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + int getConnectionState() { + String currentState = "Unknown"; + if (getCurrentState() != null) { + currentState = getCurrentState().getName(); + } + switch (currentState) { + case "Disconnected": + log("Disconnected"); + return BluetoothProfile.STATE_DISCONNECTED; + case "Disconnecting": + log("Disconnecting"); + return BluetoothProfile.STATE_DISCONNECTING; + case "Connecting": + log("Connecting"); + return BluetoothProfile.STATE_CONNECTING; + case "Connected": + case "ConnectedProcessing": + log("connected"); + return BluetoothProfile.STATE_CONNECTED; + default: + Log.e(TAG, "Bad currentState: " + currentState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + BluetoothDevice getDevice() { + return mDevice; + } + + synchronized boolean isConnected() { + return getCurrentState() == mConnected; + } + + public static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case CONNECTION_STATE_CHANGED: + return "CONNECTION_STATE_CHANGED"; + case GATT_TXN_PROCESSED: + return "GATT_TXN_PROCESSED"; + case READ_BASS_CHARACTERISTICS: + return "READ_BASS_CHARACTERISTICS"; + case START_SCAN_OFFLOAD: + return "START_SCAN_OFFLOAD"; + case STOP_SCAN_OFFLOAD: + return "STOP_SCAN_OFFLOAD"; + case ADD_BCAST_SOURCE: + return "ADD_BCAST_SOURCE"; + case SELECT_BCAST_SOURCE: + return "SELECT_BCAST_SOURCE"; + case UPDATE_BCAST_SOURCE: + return "UPDATE_BCAST_SOURCE"; + case SET_BCAST_CODE: + return "SET_BCAST_CODE"; + case REMOVE_BCAST_SOURCE: + return "REMOVE_BCAST_SOURCE"; + case PSYNC_ACTIVE_TIMEOUT: + return "PSYNC_ACTIVE_TIMEOUT"; + case CSIP_CONNECTION_STATE_CHANGED: + return "CSIP_CONNECTION_STATE_CHANGED"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + default: + break; + } + return Integer.toString(what); + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mDevice: " + mDevice); + ProfileService.println(sb, " StateMachine: " + this); + // Dump the state machine logs + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + super.dump(new FileDescriptor(), printWriter, new String[]{}); + printWriter.flush(); + stringWriter.flush(); + ProfileService.println(sb, " StateMachineLog:"); + Scanner scanner = new Scanner(stringWriter.toString()); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + ProfileService.println(sb, " " + line); + } + scanner.close(); + } + + @Override + protected void log( String msg) { + if (BASS_DBG) { + super.log(msg); + } + } + + private static void logByteArray(String prefix, byte[] value, int offset, int count) { + StringBuilder builder = new StringBuilder(prefix); + for (int i = offset; i < count; i++) { + builder.append(String.format("0x%02X", value[i])); + if (i != value.length - 1) { + builder.append(", "); + } + } + Log.d(TAG, builder.toString()); + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java new file mode 100644 index 0000000000000000000000000000000000000000..fd2be7c6623ca3745cf2ea13b993b791c7294b94 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Bass CSET managet StateMachine. There is one instance per Coordinated "set Id". + * - "Idle" and "Locked" are steady states. + * - "Locking" is a transient states until the + * Locking confirmation comes from upper layers. + * - Once lock is acquired, profile dont try to unlock + * + * (Idle) + * | ^ + * LOCK | | UNLOCK + * V | + * (Locking)<->(Unlocking) + * | ^ + * ON_LOCK | | ON_UNLOCK + * V | + * (Locked) + * + * + */ + +package com.android.bluetooth.bc; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; + +///*_CSIP +//CSET +import android.bluetooth.BluetoothDeviceGroup; +import com.android.bluetooth.groupclient.GroupService; +//_CSIP*/ + +import android.bluetooth.IBluetoothManager; +import android.os.ServiceManager; +import android.os.IBinder; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import android.os.SystemProperties; +import android.os.ParcelUuid; + +final class BassCsetManager extends StateMachine { + private static final String TAG = "BassCsetManager"; + + //Considered as Coordinated ops + static final int BASS_GRP_START_SCAN_OFFLOAD = 6; + static final int BASS_GRP_STOP_SCAN_OFFLOAD = 7; + static final int BASS_GRP_ADD_BCAST_SOURCE = 9; + static final int BASS_GRP_UPDATE_BCAST_SOURCE = 10; + static final int BASS_GRP_SET_BCAST_CODE = 11; + static final int BASS_GRP_REMOVE_BCAST_SOURCE = 12; + + static final int LOCK = 17; + static final int UNLOCK = 18; + static final int LOCK_STATE_CHANGED = 3; + static final int LOCK_TIMEOUT = 4; + static final int ON_CSIP_CONNECTED = 5; + + //10 secs time out for all gatt writes + static final int LOCK_TIMEOUT_MS = 10000; + + + @VisibleForTesting + private static final int CONNECT_TIMEOUT = 201; + + private final Idle mIdle; + private final Locking mLocking; + private final Locked mLocked; + private final LockedProcessing mLockedProcessing; + private final Unlocking mUnlocking; + + private BCService mBCService; + private final BluetoothDevice mDevice; + private final int mSetId; + private List mMemberDevices = null; + + ///*_CSIP + //CSIP Locking Interfaces + private GroupService mSetCoordinator = GroupService.getGroupService(); + //_CSIP*/ + + BassCsetManager(int setId, BluetoothDevice masterDevice, BCService svc, + Looper looper) { + super(TAG, looper); + mSetId = setId; + mBCService = svc; + + mIdle = new Idle(); + mLocked = new Locked(); + mLockedProcessing = new LockedProcessing(); + mLocking = new Locking(); + mUnlocking = new Unlocking(); + + addState(mIdle); + addState(mLocking); + addState(mLocked); + addState(mLockedProcessing); + addState(mUnlocking); + + setInitialState(mIdle); + mDevice = masterDevice; + mMemberDevices = new ArrayList(); + + } + + static BassCsetManager make(int setId, BluetoothDevice masterDevice, BCService svc, + Looper looper) { + Log.d(TAG, "make for setId, setId " + setId + ": masterDevice" + masterDevice); + BassCsetManager BassclientSm = new BassCsetManager(setId, masterDevice, svc, + looper); + BassclientSm.start(); + return BassclientSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + } + + @VisibleForTesting + class Idle extends State { + @Override + public void enter() { + log( "Enter Idle(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + mMemberDevices = null; + + } + + @Override + public void exit() { + log("Exit Idle(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Idle process message(" + mSetId + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + deferMessage(message); + //Intentional miss of break + case LOCK: + //treat Connect & Lock as same request + log("Locking to " + mSetId); + //get CSIP connection status for BluetoothDevice + //if CSIP disconnected: start Connect Procedure (mostly hpns only at first time) + //if CSIP connected: start Lock Procedure + ///*_CSIP + mSetCoordinator.setLockValue(mBCService.mCsipAppId, mSetId, null, BluetoothDeviceGroup.ACCESS_GRANTED); + //_CSIP*/ + transitionTo(mLocking); + + //transitionTo(mLocked); + break; + case UNLOCK: + Log.w(TAG, "Idle: UNLOCK ignored: " + mSetId); + break; + case LOCK_STATE_CHANGED: + //This most likely not happen + ///*_CSIP + int value = (int)message.arg1; + List devices = (List)message.obj; + Log.w(TAG, "Lock state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_GRANTED) { + transitionTo(mLocked); + } else { + Log.w(TAG, "Idle: Lock failed to " + mSetId); + } + //_CSIP*/ + break; + case ON_CSIP_CONNECTED: + //starts the Lock procedure + //Only reason why we Connect is to Lock + // + //Dont transition the state + default: + log("Idle: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class Locking extends State { + @Override + public void enter() { + log( "Enter Locking(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public void exit() { + log("Exit Locking(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Locking process message(" + mSetId + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + deferMessage(message); + break; + case LOCK: + log("Already Locking to " + mSetId); + log("Ignore this Lock request " + mSetId); + break; + case UNLOCK: + Log.w(TAG, "Locking: UNLOCK deferred: " + mSetId); + deferMessage(message); + break; + case LOCK_STATE_CHANGED: + ///*_CSIP + int value = (int)message.arg1; + Log.w(TAG, "Lock state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_GRANTED) { + List devices = (List)message.obj; + mMemberDevices = devices; + transitionTo(mLocked); + } else { + Log.w(TAG, "Locking: Unlocked to " + mSetId); + transitionTo(mIdle); + } + //_CSIP*/ + break; + case ON_CSIP_CONNECTED: + //starts the Lock procedure + //Only reason why we Connect is to Lock + // + //Dont transition the state + break; + default: + log("LOCKING: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class Locked extends State { + @Override + public void enter() { + log( "Enter Locked(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + + removeDeferredMessages(LOCK); + + } + + @Override + public void exit() { + log("Exit Locked(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Locked process message(" + mSetId + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case LOCK: + Log.w(TAG, "Locked: Lock ignored: " + mSetId); + break; + case UNLOCK: + log("Unlocking from " + mDevice); + //trigger unlock procedure + ///*_CSIP + mSetCoordinator.setLockValue(mBCService.mCsipAppId, mSetId, null, BluetoothDeviceGroup.ACCESS_RELEASED); + transitionTo(mUnlocking); + //_CSIP*/ + + //transitionTo(mIdle); + break; + case LOCK_STATE_CHANGED: + ///*_CSIP + int value = (int)message.arg1; + List devices = (List)message.obj; + Log.w(TAG, "Lock state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_GRANTED) { + transitionTo(mLocked); + } else { + Log.w(TAG, "Locking: Unlocked to " + mSetId); + transitionTo(mIdle); + } + //_CSIP*/ + break; + case BASS_GRP_START_SCAN_OFFLOAD: + if (mBCService != null) { + log("START_SCAN_OFFLOAD: " + mMemberDevices); + mBCService.startScanOffload(mDevice, mMemberDevices); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_STOP_SCAN_OFFLOAD: + if (mBCService != null) { + log("STOP_SCAN_OFFLOAD: " + mMemberDevices); + mBCService.stopScanOffload(mDevice, mMemberDevices); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_ADD_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + if (mBCService != null) { + mBCService.addBroadcastSource(mDevice, mMemberDevices, srcInfo); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_UPDATE_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + if (mBCService != null) { + mBCService.updateBroadcastSource(mDevice, mMemberDevices, srcInfo); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_SET_BCAST_CODE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + if (mBCService != null) { + mBCService.setBroadcastCode(mDevice, mMemberDevices, srcInfo); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_REMOVE_BCAST_SOURCE: + byte sourceId = (byte)message.arg1; + if (mBCService != null) { + mBCService.removeBroadcastSource(mDevice, mMemberDevices, sourceId); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + default: + log("Locked: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class LockedProcessing extends State { + @Override + public void enter() { + log( "Enter LockedProcessing(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public void exit() { + log("Exit LockedProcessing(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("LockedProcessing process message(" + mSetId + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case UNLOCK: + log("LockedProcessing: UNLOCK defer " + mDevice); + deferMessage(message); + transitionTo(mLocked); + break; + case LOCK_STATE_CHANGED: + int value = (int)message.arg1; + Log.w(TAG, "Locking state changed:" + value); + //Should never happen + break; + case LOCK: + log("LockedProcessing: LOCK ignore " + mDevice); + break; + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + if (hasDeferredMessages(UNLOCK)) { + //If Unlock is in pending list, remove it + //Override the UNLOCK with this new operation + log("removing the unlock message, as there is another req"); + removeDeferredMessages(UNLOCK); + } + deferMessage(message); + break; + default: + log("LockedProcessing: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + @VisibleForTesting + class Unlocking extends State { + @Override + public void enter() { + log( "Enter Unlocking(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + + //removeDeferredMessages(LOCK); + + } + + @Override + public void exit() { + log("Exit Unlocking(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Locked process message(" + mSetId + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case UNLOCK: + log("Unlocking: UNLOCK ignored from " + mDevice); + break; + case LOCK_STATE_CHANGED: + ///*_CSIP + int value = (int)message.arg1; + Log.w(TAG, "Locking state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_RELEASED) { + mMemberDevices = null; + transitionTo(mIdle); + } else { + Log.w(TAG, "UnLocking: failed to " + mSetId); + //keep that back in Locked? + transitionTo(mLocked); + // + } + //_CSIP*/ + break; + case LOCK: + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + deferMessage(message); + break; + default: + log("Locked: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + private static String messageWhatToString(int what) { + switch (what) { + case LOCK: + return "LOCK"; + case UNLOCK: + return "UNLOCK"; + case LOCK_STATE_CHANGED: + return "LOCK_STATE_CHANGED"; + case BASS_GRP_START_SCAN_OFFLOAD: + return "BASS_GRP_START_SCAN_OFFLOAD"; + case BASS_GRP_STOP_SCAN_OFFLOAD: + return "BASS_GRP_STOP_SCAN_OFFLOAD"; + case BASS_GRP_ADD_BCAST_SOURCE: + return "BASS_GRP_ADD_BCAST_SOURCE"; + case BASS_GRP_UPDATE_BCAST_SOURCE: + return "BASS_GRP_UPDATE_BCAST_SOURCE"; + case BASS_GRP_SET_BCAST_CODE: + return "BASS_GRP_SET_BCAST_CODE"; + case BASS_GRP_REMOVE_BCAST_SOURCE: + return "BASS_GRP_REMOVE_BCAST_SOURCE"; + default: + break; + } + return Integer.toString(what); + } + + @Override + protected void log( String msg) { + if (BassClientStateMachine.BASS_DBG) { + super.log(msg); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..9289198a44f4fc1d542bdd3a3649b7e99b72a60a --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java @@ -0,0 +1,506 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package com.android.bluetooth.bc; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; + +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import java.nio.charset.StandardCharsets; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import java.util.NoSuchElementException; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.BluetoothLeScanner; +import java.util.UUID; +import android.os.Handler; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.RemoteException; + +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +//import android.bluetooth.BluetoothBroadcast; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import com.android.bluetooth.btservice.ServiceFactory; +///*_BMS +import com.android.bluetooth.broadcast.BroadcastService; +//_BMS*/ +import android.bluetooth.BluetoothCodecConfig; +/*_PACS +import com.android.bluetooth.pacsclient.PacsClientService; +_PACS*/ +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; + +/** + * Bass Utility functions + */ + +final class BassUtils { + private static final String TAG = "BassUtils"; + /*LE Scan related members*/ + private boolean mBroadcastersAround = false; + private BluetoothAdapter mBluetoothAdapter = null; + private BluetoothLeScanner mLeScanner = null; + private BCService mBCService = null; + + ///*_BMS + private BroadcastService mBAService = null; + //_BMS*/ + public static final String BAAS_UUID = "00001852-0000-1000-8000-00805F9B34FB"; + private boolean mIsLocalBMSNotified = false; + private ServiceFactory mFactory = new ServiceFactory(); + //Using ArrayList as KEY to hashmap. May be not risk + //in this case as It is used to track the callback to cancel Scanning later + private final Map, ScanCallback> mLeAudioSourceScanCallbacks; + private final Map mBassAutoAssist; + + private static final int AA_START_SCAN = 1; + private static final int AA_SCAN_SUCCESS = 2; + private static final int AA_SCAN_FAILURE = 3; + private static final int AA_SCAN_TIMEOUT = 4; + //timeout for internal scan + private static final int AA_SCAN_TIMEOUT_MS = 1000; + + /** + * Stanadard Codec param types + */ + static final int LOCATION = 3; + //sample rate + static final int SAMPLE_RATE = 1; + //frame duration + static final int FRAME_DURATION = 2; + //Octets per frame + static final int OCTETS_PER_FRAME = 8; + /*_PACS + private PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); + _PACS*/ + BassUtils (BCService service) { + mBCService = service; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); + mLeAudioSourceScanCallbacks = new HashMap, ScanCallback>(); + mBassAutoAssist = new HashMap(); + ///*_BMS + mBAService = BroadcastService.getBroadcastService(); + //_BMS*/ + } + + private ScanCallback mPaSyncScanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + log( "onScanResult:" + result); + } + }; + + void cleanUp () { + + if (mLeAudioSourceScanCallbacks != null) { + mLeAudioSourceScanCallbacks.clear(); + } + + if (mBassAutoAssist != null) { + mBassAutoAssist.clear(); + } + } + + boolean leScanControl(boolean on) { + log("leScanControl:" + on); + mLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); + if (mLeScanner == null) { + Log.e(TAG, "LeScan handle not available"); + return false; + } + + if (on) { + mLeScanner.startScan(mPaSyncScanCallback); + } else { + mLeScanner.stopScan(mPaSyncScanCallback); + } + + return true; + } + + /* private helper to check if the Local BLE Broadcast happening Or not */ + public boolean isLocalLEAudioBroadcasting() { + boolean ret = false; + /*String localLeABroadcast = SystemProperties.get("persist.vendor.btstack.isLocalLeAB"); + if (!localLeABroadcast.isEmpty() && "true".equals(localLeABroadcast)) { + ret = true; + } + log("property isLocalLEAudioBroadcasting returning " + ret);*/ + ///*_Broadcast + if (mBAService == null) { + mBAService = BroadcastService.getBroadcastService(); + } + if (mBAService != null) { + ret = mBAService.isBroadcastActive(); + //ret = mBAService.isBroadcastStreaming(); + log("local broadcast streaming:" + ret); + } else { + log("BroadcastService is Null"); + } + //_Broadcast*/ + log("isLocalLEAudioBroadcasting returning " + ret); + return ret; + } + + Handler mAutoAssistScanHandler = new Handler() { + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case AA_START_SCAN: + BluetoothDevice dev = (BluetoothDevice) msg.obj; + Message m = obtainMessage(AA_SCAN_TIMEOUT); + m.obj = dev; + sendMessageDelayed(m, AA_SCAN_TIMEOUT_MS); + searchforLeAudioBroadcasters(dev, null); + break; + case AA_SCAN_SUCCESS: + //Able to find to desired desired Source Device + ScanResult scanRes = (ScanResult) msg.obj; + dev = scanRes.getDevice(); + stopSearchforLeAudioBroadcasters(dev,null); + mBCService.selectBroadcastSource(dev, scanRes, false, true); + break; + case AA_SCAN_FAILURE: + //Not able to find the given source + //ignore + break; + case AA_SCAN_TIMEOUT: + dev = (BluetoothDevice)msg.obj; + stopSearchforLeAudioBroadcasters(dev, null); + break; + } + } + }; + private void notifyLocalBroadcastSourceFound(ArrayList cbs) { + BluetoothDevice localDev = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAdapter.getAddress()); + String localName = BluetoothAdapter.getDefaultAdapter().getName(); + ScanRecord record = null; + if (localName != null) { + byte name_len = (byte)localName.length(); + byte[] bd_name = localName.getBytes(StandardCharsets.US_ASCII); + byte[] name_key = new byte[] {++name_len, 0x09 }; //0x09 TYPE:Name + byte[] scan_r = new byte[name_key.length + bd_name.length]; + System.arraycopy(name_key, 0, scan_r, 0, name_key.length); + System.arraycopy(bd_name, 0, scan_r, name_key.length, bd_name.length); + record = ScanRecord.parseFromBytes(scan_r); + log ("Local name populated in fake Scan res:" + record.getDeviceName()); + } + ScanResult scanRes = new ScanResult(localDev, + 1, 1, 1,2, 0, 0, 0, record, 0); + if (cbs != null) { + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastSourceFound(scanRes); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastSourceFound"); + } + } + } + } + public boolean searchforLeAudioBroadcasters (BluetoothDevice srcDevice, + ArrayList cbs + ) { + log( "searchforLeAudioBroadcasters: "); + BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); + mIsLocalBMSNotified = false; + if (scanner == null) { + Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); + return false; + } + synchronized (mLeAudioSourceScanCallbacks) { + if (mLeAudioSourceScanCallbacks.containsKey(cbs)) { + Log.e(TAG, "LE Scan has already started"); + return false; + } + ScanCallback scanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + log( "onScanResult:" + result); + if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) { + // Should not happen. + Log.e(TAG, "LE Scan has already started"); + return; + } + ScanRecord scanRecord = result.getScanRecord(); + //int pInterval = result.getPeriodicAdvertisingInterval(); + if (scanRecord != null) { + Map listOfUuids = scanRecord.getServiceData(); + if (listOfUuids != null) { + //ParcelUuid bmsUuid = new ParcelUuid(BroadcastService.BAAS_UUID); + //boolean isBroadcastSource = listOfUuids.containsKey(bmsUuid); + boolean isBroadcastSource = listOfUuids.containsKey(ParcelUuid.fromString(BAAS_UUID)); + log( "isBroadcastSource:" + isBroadcastSource); + if (isBroadcastSource) { + log( "Broadcast Source Found:" + result.getDevice()); + if (cbs != null) { + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastSourceFound(result); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastSourceFound"); + } + } + } else { + if (srcDevice.equals(result.getDevice())) { + log("matching src Device found"); + Message msg = mAutoAssistScanHandler.obtainMessage(AA_SCAN_SUCCESS); + msg.obj = result; + mAutoAssistScanHandler.sendMessage(msg); + } + } + } else { + log( "Broadcast Source UUID not preset, ignore"); + } + } else { + Log.e(TAG, "Ignore no UUID"); + return; + } + } else { + Log.e(TAG, "Scan record is null, ignoring this Scan res"); + return; + } + //Before starting LE Scan, Call local APIs to find out if the local device + //is Broadcaster, then generate callback for Local device + if (!mIsLocalBMSNotified && isLocalLEAudioBroadcasting()) { + //Create a DUMMY scan result for colocated case + notifyLocalBroadcastSourceFound(cbs); + mIsLocalBMSNotified = true; + } + } + + public void onScanFailed(int errorCode) { + Log.e(TAG, "Scan Failure:" + errorCode); + } + }; + if (mBluetoothAdapter != null) { + if (cbs != null) { + mLeAudioSourceScanCallbacks.put(cbs, scanCallback); + } else { + //internal auto assist trigger remember it + //based on device + mBassAutoAssist.put(srcDevice, scanCallback); + } + + ScanSettings settings = new ScanSettings.Builder().setCallbackType( + ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setLegacy(false) + .build(); + ScanFilter.Builder filterBuilder = new ScanFilter.Builder(); + ScanFilter srcFilter = filterBuilder.setServiceUuid( + ParcelUuid.fromString(BAAS_UUID)).build(); + List filters = new ArrayList(); + if (!mIsLocalBMSNotified && isLocalLEAudioBroadcasting()) { + //Create a DUMMY scan result for colocated case + notifyLocalBroadcastSourceFound(cbs); + mIsLocalBMSNotified = true; + } + scanner.startScan(filters, settings, scanCallback); + return true; + } else { + Log.e(TAG, "searchforLeAudioBroadcasters: Adapter is NULL"); + return false; + } + } + } + public boolean stopSearchforLeAudioBroadcasters(BluetoothDevice srcDev, + ArrayList cbs) { + log( "stopSearchforLeAudioBroadcasters()"); + BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); + if (scanner == null) { + return false; + } + ScanCallback scanCallback = null; + if (cbs != null) { + scanCallback = mLeAudioSourceScanCallbacks.remove(cbs); + } else { + scanCallback = mLeAudioSourceScanCallbacks.remove(srcDev); + } + + if (scanCallback == null) { + log( "scan not started yet"); + return false; + } + scanner.stopScan(scanCallback); + return true; + } + + private int convertConfigurationSRToCapabilitySR(byte sampleRate) { + int ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; + switch (sampleRate) { + case 1: + ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break; + case 2: + ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break; + case 3: + ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break; + case 4: + //ret = BluetoothCodecConfig.SAMPLE_RATE_32000; break; + case 5: + ret = BluetoothCodecConfig.SAMPLE_RATE_44100; break; + case 6: + ret = BluetoothCodecConfig.SAMPLE_RATE_48000; break; + } + log("convertConfigurationSRToCapabilitySR returns:" + ret); + return ret; + } + + private boolean isSampleRateSupported(BluetoothDevice device, byte sampleRate) { + boolean ret = false; + /*_PACS + BluetoothCodecConfig[] supportedConfigs = mPacsClientService.getSinkPacs(device); + int actualSampleRate = convertConfigurationSRToCapabilitySR(sampleRate); + + if (actualSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) { + return false; + } + + for (int i=0; i selectBises(BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, BaseData base) { + boolean noPref = SystemProperties.getBoolean("persist.vendor.service.bt.bass_no_pref", false); + if (noPref) { + log("No pref selected"); + return null; + } else { + /*_PACS + mPacsClientService = PacsClientService.getPacsClientService(); + List bChannels = new ArrayList(); + //if (mPacsClientService == null) { + log("selectBises: Pacs Service is null, pick BISes apropriately"); + //Pacs not available + if (base != null) { + bChannels = base.pickAllBroadcastChannels(); + } else { + bChannels = null; + } + return bChannels; + //} + if (mPacsClientService != null) { + int supportedLocations = 1/*mPacsClientService.getSinkLocations(device); + ArrayList broadcastedCodecInfo = base.getBISIndexInfos(); + if (broadcastedCodecInfo != null) { + for (int i=0; i consolidatedUniqueCodecInfo = broadcastedCodecInfo.get(i).consolidatedUniqueCodecInfo; + byte index = broadcastedCodecInfo.get(i).index; + if (consolidatedUniqueCodecInfo != null) { + + + byte[] bisChannelLocation = consolidatedUniqueCodecInfo.get(LOCATION).getBytes(); + byte[] locationValue = new byte[4]; + System.arraycopy(bisChannelLocation, 2, locationValue, 0, 4); + log ("locationValue>>> "); + printByteArray(locationValue); + ByteBuffer wrapped = ByteBuffer.wrap(locationValue); + int bisLocation = wrapped.getInt(); + log("bisLocation: " + bisLocation); + int reversebisLoc = Integer.reverseBytes(bisLocation); + log("reversebisLoc: " + reversebisLoc); + + byte[] bisSampleRate = consolidatedUniqueCodecInfo.get(SAMPLE_RATE).getBytes(); + byte bisSR = bisSampleRate[2]; + + //using bitwise operand as Location can be bitmask + if (isSampleRateSupported(device, bisSR) && (reversebisLoc & supportedLocations) == supportedLocations) { + log("matching location: bisLocation " + reversebisLoc + ":: " + supportedLocations); + BleBroadcastSourceChannel bc = new BleBroadcastSourceChannel(index, String.valueOf(index), true); + bChannels.add(bc); + } + } + } + } + } + + if (bChannels != null && bChannels.size() == 0) { + log("selectBises: no channel are selected"); + bChannels = null; + + } + return bChannels; + _PACS*/ + } + return null; + } + + public void triggerAutoAssist (BleBroadcastSourceInfo srcInfo) { + //searchforLeAudioBroadcasters (srcInfo.getSourceDevice(), null, AUTO_ASSIST_SCAN_TIMEOUT); + BluetoothDevice dev = srcInfo.getSourceDevice(); + + Message msg = mAutoAssistScanHandler.obtainMessage(AA_START_SCAN); + msg.obj = srcInfo.getSourceDevice(); + mAutoAssistScanHandler.sendMessage(msg); + } + + static void log(String msg) { + if (BassClientStateMachine.BASS_DBG) { + Log.d(TAG, msg); + } + } + + static void printByteArray(byte[] array) { + log("Entire byte Array as string: " + Arrays.toString(array)); + log("printitng byte by bte"); + for (int i=0; i mBisInfo; + MapmMetaInfo = Collections.synchronizedMap(new HashMap<>());; + private String mAdvAddress; + private int mAdvAddressType; + private BluetoothLeAdvertiser mAdvertiser; + private BluetoothCodecStatus mCodecStatus; + private BluetoothCodecConfig mCodecConfig; + private BluetoothCodecConfig mHapCodecConfig; + private BroadcastCodecConfig mBroadcastCodecConfig; + private BroadcastAdvertiser mBroadcastAdvertiser; + private int mBroadcastConfigSettings; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mBroadcastDevice = null; + private boolean mBroadcastDeviceIsActive = false; + TrackMetadata mTrackMetadata; + private String mBroadcastAddress = "FA:CE:FA:CE:FA:CE"; + ActiveDeviceManagerService mActiveDeviceManager; + public static UUID BROADCAST_AUDIO_UUID = UUID.fromString("00001852-0000-1000-8000-00805F9B34FB"); + public static UUID BASIC_AUDIO_UUID = UUID.fromString("00001851-0000-1000-8000-00805F9B34FB"); + private BroadcastBase mBroadcastBase; + private MediaAudio mMediaAudio; + private boolean new_codec_id = false; + private static int mSecPhy = 1; + private static int mTxPowerLevel = 1; + private static int mPaInt; + private boolean mNewVersion = false; + List broadcast_supported_config = new ArrayList(List.of("16_2", "24_2", "48_1", "48_2", "48_3", "48_4", "48_5", "48_6")); + private static final int MSG_ENABLE_BROADCAST = 1; + private static final int MSG_DISABLE_BROADCAST = 2; + private static final int MSG_SET_ENCRYPTION_KEY = 3; + private static final int MSG_GET_ENCRYPTION_KEY = 4; + private static final int MSG_SET_BROADCAST_ACTIVE = 5; + private static final int MSG_UPDATE_BROADCAST_ADV_SET = 6; + private static final int MSG_ADV_DATA_SET = 7; + private static final int MSG_SET_AUDIO_PATH = 8; + private static final int MSG_RESET_ENCRYPTION_FLAG_TIMEOUT = 9; + private static final int MSG_FROM_NATIVE_CODEC_STATE = 10; + private static final int MSG_FROM_NATIVE_BROADCAST_STATE = 11; + private static final int MSG_FROM_NATIVE_ENCRYPTION_KEY = 12; + private static final int MSG_FROM_NATIVE_BROADCAST_AUDIO_STATE = 13; + private static final int MSG_FROM_NATIVE_SETUP_BIG = 14; + private static final int MSG_UPDATE_BROADCAST_STATE = 15; + private static final int MSG_FROM_NATIVE_BROADCAST_ID = 16; + @Override + protected IProfileServiceBinder initBinder() { + return new BluetoothBroadcastBinder(this); + } + + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + if (sBroadcastService != null) { + Log.w(TAG, "Broadcastervice is already running"); + return true; + } + if (mHandler != null) + mHandler = null; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when A2dpService starts"); + + mBroadcastNativeInterface = Objects.requireNonNull(mBroadcastNativeInterface.getInstance(), + "BroadcastNativeInterface cannot be null when BroadcastService starts"); + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when A2dpService starts"); + HandlerThread thread = new HandlerThread("BroadcastHandler"); + mBroadcastConfigSettings = SystemProperties.getInt("persist.vendor.btstack.bap_ba_setting", 4); + mBroadcastCodecConfig = new BroadcastCodecConfig(); + String PartialSimulcast = SystemProperties.get("persist.vendor.btstack.partial_simulcast"); + if (!PartialSimulcast.isEmpty() && "true".equals(PartialSimulcast)) { + mPartialSimulcast = true; + mNumSubGrps = 2; + mNumBises = 4; + //mHapCodecConfig = new BroadcastCodecConfig(mPartialSimulcast); + } + String mNewCodecId = SystemProperties.get("persist.vendor.btstack.new_lc3_id"); + if (mNewCodecId.isEmpty() || "true".equals(mNewCodecId) || + "6".equals(mNewCodecId)) { + new_codec_id = true; + } + /* Property to set seconday advertising phy to 1M or 2M. 2M is selected by default + * if propety is not set + */ + mSecPhy = SystemProperties.getInt("persist.vendor.btstack.secphy", 2); + mTxPowerLevel = SystemProperties.getInt("persist.vendor.service.bt.txpower", 9); + mPD = SystemProperties.getInt("persist.vendor.service.bt.presentation_delay", 40); + mPaInt = SystemProperties.getInt("persist.vendor.btstack.pa_interval", 360); + mNewVersion = SystemProperties.getBoolean("persist.vendor.service.bt.new_ba_version", true); + int offload_mode = 1; //offload + mBroadcastNativeInterface.init(1, mCodecConfig,offload_mode); + thread.start(); + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + mAdapterService.registerReceiver(mBroadcastReceiver, filter); + Looper looper = thread.getLooper(); + mHandler = new BroadcastMessageHandler(looper); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBroadcastBase = new BroadcastBase(); + mBisInfo = new ArrayList<>(); + //mBroadcastAdvertiser = new BroadcastAdvertiser(); + setBroadcastService(this); + mBroadcastDevice = mAdapter.getRemoteDevice(mBroadcastAddress); + mTrackMetadata = new TrackMetadata(null); + + mActiveDeviceManager = ActiveDeviceManagerService.get(this); + DeviceProfileMap dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + dpm.profileConnectionUpdate(mBroadcastDevice, ApmConst.AudioFeatures.BROADCAST_AUDIO, ApmConst.AudioProfiles.BROADCAST_LE, true); + + //Get current codec and call native init + return true; + } + private void initialize_advertiser() { + Log.d(TAG,"initalize_advertiser"); + mBroadcastAdvertiser = new BroadcastAdvertiser(); + GetEncryptionKeyFromNative(); + } + private void startAdvTest() { + //Log.d(TAG,"startAdvTest!!!"); + boolean ba_test = SystemProperties.getBoolean("persist.vendor.btstack.batest",false); + if (ba_test) { + Log.d(TAG,"startAdvTest!!!"); + EnableBroadcast(null); + } + } + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.ERROR); + Log.d(TAG,"action: " + action + " state: " + state); + if (state == BluetoothAdapter.STATE_ON) { + initialize_advertiser(); + startAdvTest(); + } else if (state == BluetoothAdapter.STATE_TURNING_OFF) { + if (sBroadcastService != null) + cleanup_broadcast(); + } + } + } + }; + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sBroadcastService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + notifyBroadcastEnabled(false); + if (mIsAdvertising) { + mBroadcastAdvertiser.stopBroadcastAdvertising(); + } + mAdapterService = null; + mBroadcastNativeInterface = null; + mAudioManager = null; + mIsAdvertising = false; + Looper looper = mHandler.getLooper(); + if (looper != null) { + looper.quit(); + } + setBroadcastService(null); + return true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + } + public static synchronized BroadcastService getBroadcastService() { + if (sBroadcastService == null) { + Log.w(TAG, "getBroadcastService(): service is null"); + return null; + } + if (!sBroadcastService.isAvailable()) { + Log.w(TAG, "getBroadcastService(): service is not available"); + return null; + } + return sBroadcastService; + } + + /** Handles Broadcast messages. */ + private final class BroadcastMessageHandler extends Handler { + private BroadcastMessageHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + Log.v(TAG, "BroadcastMessageHandler: received message=" + msg.what); + int prev_state; + switch (msg.what) { + case MSG_ENABLE_BROADCAST: + //int prev_state; + synchronized (mBroadcastLock) { + if (VDBG) { + Log.i(TAG, "Setting broadcast state to ENABLING"); + } + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_ENABLING; + } + broadcastState(mBroadcastState, prev_state); + mBroadcastNativeInterface.enableBroadcast(mCodecConfig); + break; + case MSG_DISABLE_BROADCAST: + //int prev_state; + goingDown = true; + if (!mIsAdvertising) { + Log.e(TAG, "Broadcast is not advertising"); + break; + } + synchronized(mBroadcastLock) { + if (VDBG) { + Log.i(TAG,"Disabling broadcast, setting state to DISABLING"); + } + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLING; + } + broadcastState(mBroadcastState, prev_state); + mBroadcastNativeInterface.disableBroadcast(mAdvertisingSet.getAdvertiserId()); + //mBroadcastAdvertiser.stopBroadcastAdvertising(); + break; + case MSG_SET_ENCRYPTION_KEY: + //int length = msg.arg1; + mBroadcastNativeInterface.SetEncryptionKey(mEncryptionEnabled, mEncryptionLength); + if (mEncryptionLength == 0) { + for(int i = 0; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + broadcastEncryptionkeySet(); + } + break; + case MSG_GET_ENCRYPTION_KEY: { + mEncryptionString = mBroadcastNativeInterface.GetEncryptionKey(); + if (mEncryptionString == null) { + Log.e(TAG,"MSG_GET_ENCRYPTION_KEY: mEncryptionString null"); + for (int i = 0; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + break; + } + mEncKey= mEncryptionString.getBytes(); + Log.i(TAG, "mEncryptionString: " + mEncryptionString); + System.arraycopy(mEncKey, 0, BigBroadcastCode, 0, mEncKey.length); + if (mEncKey.length < mDefaultEncryptionLength) { + for (int i = mEncKey.length; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + } + for (int i = 0;i < mDefaultEncryptionLength/2; i++) { + byte temp = BigBroadcastCode[i]; + BigBroadcastCode[i] = BigBroadcastCode[(mDefaultEncryptionLength -1) - i]; + BigBroadcastCode[(mDefaultEncryptionLength -1) - i] = temp; + } + for (int i = 0; i < 16; i++) { + Log.i(TAG,"BigBroadcastCode["+ i + "] = " + BigBroadcastCode[i]); + } + //TODO: Stub to test encryption key creation, to be removed + //Log.i(TAG,"calling setencryptionkey"); + //mBroadcastNativeInterface.SetEncryptionKey(4); + broadcastEncryptionkeySet(); + } + break; + case MSG_UPDATE_BROADCAST_ADV_SET: + break; + case MSG_SET_BROADCAST_ACTIVE: + // Call native layer to set broadcast active + //mBroadcastNativeInterface.setActiveDevice(true, mAdvertisingSet.getAdvertiserId()); + //setActiveDevice(mBroadcastDevice); + notifyBroadcastEnabled(true); + break; + case MSG_RESET_ENCRYPTION_FLAG_TIMEOUT: + Log.i(TAG,"Setting mEncKeyRefreshed to false"); + mEncKeyRefreshed = false; + break; + case MSG_FROM_NATIVE_BROADCAST_STATE: + synchronized(mBroadcastLock) { + prev_state = mBroadcastState; + mBroadcastState = msg.arg1; + if (VDBG) { + Log.i(TAG,"New broadcast state: " + mBroadcastState); + } + } + if (mBroadcastState == BluetoothBroadcast.STATE_DISABLED) { + if (goingDown) { + notifyBroadcastEnabled(false); + } + mBIGHandle = -1; + mBroadcastAdvertiser.stopBroadcastAdvertising(); + break; + } + if (prev_state != mBroadcastState) + broadcastState(mBroadcastState, prev_state); + break; + case MSG_ADV_DATA_SET: + synchronized (mBroadcastLock) { + if (VDBG) { + Log.i(TAG, "Setting broadcast state to ENABLING"); + } + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_ENABLED; + } + broadcastState(mBroadcastState, prev_state); + break; + case MSG_SET_AUDIO_PATH: + //mBroadcastNativeInterface.SetupAudioPath(true,mAdvertisingSet.getAdvertiserId(),mBIGHandle,mNumBises,bis_handles); + break; + case MSG_FROM_NATIVE_CODEC_STATE: + mCodecStatus = (BluetoothCodecStatus)msg.obj; + if (IsCodecConfigChanged(mCodecStatus.getCodecConfig())) { + mBroadcastCodecConfig.updateBroadcastCodecConfig(mCodecStatus.getCodecConfig()); + mBroadcastBase.populateBase(); + mBroadcastAdvertiser.updatePAwithBase(); + } + broadcastCodecConfig(mCodecStatus); + mMediaAudio = MediaAudio.get(); + mMediaAudio.onCodecConfigChange(mBroadcastDevice, mCodecStatus, ApmConst.AudioProfiles.BROADCAST_LE); + break; + case MSG_FROM_NATIVE_ENCRYPTION_KEY: { + mEncryptionString = (String)msg.obj; + Log.d(TAG,"mEncryptionString: " + mEncryptionString); + mEncKey= mEncryptionString.getBytes(); + System.arraycopy(mEncKey, 0, BigBroadcastCode, 0, mEncKey.length); + if (mEncKey.length < mDefaultEncryptionLength) { + for (int i = mEncKey.length; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + } + for (int i = 0; i < mEncKey.length; i++) { + Log.d(TAG,"mEnc[" + i +"] = " + mEncKey[i]); + } + for (int i = 0;i < mDefaultEncryptionLength/2; i++) { + byte temp = BigBroadcastCode[i]; + BigBroadcastCode[i] = BigBroadcastCode[(mDefaultEncryptionLength - 1) - i]; + BigBroadcastCode[(mDefaultEncryptionLength - 1) - i] = temp; + } + //Broadcast encyption key set + broadcastEncryptionkeySet(); + } + break; + case MSG_FROM_NATIVE_SETUP_BIG: + int setup = msg.arg1; + boolean set = (setup == 1); + if (set) { + Log.d(TAG, "BIG created: " + mBIGHandle + "with no of bises: " + mNumBises); + mNumBises = mNumBises * mNumSubGrps; + mBroadcastBase.populateBase(); + mBroadcastAdvertiser.updatePAwithBase(); + } else { + Log.d(TAG, "BIG terminated"); + mBIGHandle = -1; + //Clean up mBisInfo List + mBisInfo.clear(); + mMetaInfo.clear(); + } + break; + case MSG_FROM_NATIVE_BROADCAST_AUDIO_STATE: + int prevState = mBroadcastAudioState; + mBroadcastAudioState = msg.arg1; + if (prevState != mBroadcastAudioState) + broadcastAudioState(mBroadcastAudioState, prevState); + break; + case MSG_FROM_NATIVE_BROADCAST_ID: + if (mBroadcastAdvertiser != null) { + mBroadcastAdvertiser.startBroadcastAdvertising(); + } else { + Log.e(TAG,"Did not receive adatper state change intent, turning off Broadcast"); + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + } + break; + case MSG_UPDATE_BROADCAST_STATE: + prev_state = msg.arg1; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + Log.d(TAG,"MSG_UPDATE_BROADCAST_STATE"); + broadcastState(mBroadcastState, prev_state); + break; + default: + Log.e(TAG,"unknown message msg.what = " + msg.what); + break; + } + Log.d(TAG,"Exit handleMessage"); + } + } + + private void updateBroadcastStateToHfp(int state) { + if (DBG) { + Log.d(TAG,"updateBroadcastStateToHfp"); + } + HeadsetService hfpService = HeadsetService.getHeadsetService(); + if (hfpService != null) { + hfpService.updateBroadcastState(state); + } + } + private void broadcastState(int state, int prev_state) { + if (DBG) { + Log.d(TAG, "Broadcasting broadcastState: " + state); + } + Intent intent = new Intent(BluetoothBroadcast.ACTION_BROADCAST_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prev_state); + intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + updateBroadcastStateToHfp(state); + } + private void broadcastCodecConfig(BluetoothCodecStatus codecStatus) { + if (DBG) { + Log.d(TAG, "Broacasting broadcastCodecConfig" + codecStatus); + } + Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); + intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, codecStatus); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBroadcastDevice); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + //sendBroadcast(intent, BLUETOOTH_CONNECT); + } + + private void broadcastEncryptionkeySet() { + if (DBG) { + Log.d(TAG, "broadcastEncryptionkeySet"); + } + Intent intent = new Intent(BluetoothBroadcast.ACTION_BROADCAST_ENCRYPTION_KEY_GENERATED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private void broadcastAudioState(int newState, int prevState) { + Log.d(TAG, "broadcastAudioState: State:" + audioStateToString(prevState) + + "->" + audioStateToString(newState)); + Intent intent = new Intent(BluetoothBroadcast.ACTION_BROADCAST_AUDIO_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private static String audioStateToString(int state) { + switch (state) { + case BluetoothBroadcast.STATE_PLAYING: + return "PLAYING"; + case BluetoothBroadcast.STATE_NOT_PLAYING: + return "NOT_PLAYING"; + default: + break; + } + return Integer.toString(state); + } + private boolean IsCodecConfigChanged(BluetoothCodecConfig config) { + return (mCodecConfig.getSampleRate() != config.getSampleRate() || + mCodecConfig.getChannelMode() != config.getChannelMode() || + mCodecConfig.getCodecSpecific1() != config.getCodecSpecific1() || + mCodecConfig.getCodecSpecific2() != config.getCodecSpecific2()); + } + private boolean isCodecValid(BluetoothCodecConfig mCodecConfig) { + if (mCodecConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { + return false; + } + return true; + } + + private boolean isCodecConfigValid(String config_id) { + if (broadcast_supported_config.contains(config_id)) { + Log.d(TAG,"isCodecConfigValid: config supported"); + return true; + } + Log.d(TAG,"isCodecConfigValid: config not supported"); + return false; + } + + private boolean isEncrytionLengthValid(int enc_length) { + if (enc_length == 4 || enc_length == 16) { + return true; + } + return false; + } + + private BluetoothCodecConfig buildCodecConfig(String config_id, int channel) { + //BluetoothCodecConfig cc; + int index = broadcast_supported_config.indexOf(config_id); + int sr; + long codecspecific1, codecspecific2; + String isMono = SystemProperties.get("persist.vendor.btstack.enable.broadcast_mono"); + Log.d(TAG,"buildCodecConfig:" + config_id + " index: " + index); + switch(index) { + case 0: //16_2 + sr = BluetoothCodecConfig.SAMPLE_RATE_16000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1001;//32kbps + codecspecific2 = 1; + break; + case 1: //24_2 + sr = BluetoothCodecConfig.SAMPLE_RATE_24000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1002;//48kbps + codecspecific2 = 1; + break; + case 2: //48_1 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1004;//80kbps + codecspecific2 = 0; + break; + case 3: //48_2 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1004;//80kbps + codecspecific2 = 1; + break; + case 4: //48_3 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1006;//96kbps + codecspecific2 = 0; + break; + case 5: //48_4 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1006;//96kbps + codecspecific2 = 1; + break; + case 6: //48_5 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007;//124kbps + codecspecific2 = 0; + break; + case 7: //48_6 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007;//124kbps + codecspecific2 = 1; + break; + + default: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007;//80kbps + codecspecific2 = 1; + break; + } + //if (isMono.isEmpty() || isMono.equals("mono")) { + // ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + //} + BluetoothCodecConfig cc = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + sr, BluetoothCodecConfig.BITS_PER_SAMPLE_24, + channel, codecspecific1, codecspecific2, 0, 0); + return cc; + } + private static synchronized void setBroadcastService(BroadcastService instance) { + if (DBG) { + Log.d(TAG, "setBroadcastService(): set to: " + instance); + } + sBroadcastService = instance; + } + + private void cleanup_broadcast() { + if (DBG) Log.d (TAG, "cleanup_broadcast"); + synchronized (mBroadcastLock) { + if (mIsAdvertising) { + if (mBroadcastNativeInterface != null) + mBroadcastNativeInterface.disableBroadcast(mAdvertisingSet.getAdvertiserId()); + mBroadcastAdvertiser.stopBroadcastAdvertising(); + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + } + } + } + public boolean EnableBroadcast(String packageName) { + if (DBG) Log.d (TAG, "EnableBroadcast"); + + if (mBroadcastState != BluetoothBroadcast.STATE_DISABLED) { + return false; + } + Message msg = mHandler.obtainMessage(MSG_ENABLE_BROADCAST); + mHandler.sendMessage(msg); + return true; + } + public boolean DisableBroadcast(String packageName) { + if (DBG) Log.d (TAG, "DisableBroadcast: state " + mBroadcastState); + + if (mBroadcastState == BluetoothBroadcast.STATE_DISABLING || + mBroadcastState == BluetoothBroadcast.STATE_DISABLED) { + return true; + } else if (mBroadcastState != BluetoothBroadcast.STATE_ENABLED && + mBroadcastState != BluetoothBroadcast.STATE_STREAMING) { + Log.d(TAG,"Broadcast is not enabled yet"); + return false; + } + Message msg = mHandler.obtainMessage(MSG_DISABLE_BROADCAST); + mHandler.sendMessage(msg); + return true; + } + public boolean SetEncryption(boolean enable, int enc_len, + boolean use_existing, String packageName) { + if (DBG) Log.d (TAG,"SetEncryption"); + + mEncryptionEnabled = enable; + if (enable) { + if (!isEncrytionLengthValid(enc_len)) { + if (DBG) Log.d (TAG,"SetEncryption: invalid encrytion length requested"); + return false; + } + } else { + Log.d(TAG,"Selected unencrypted"); + enc_len = 0; + } + if (!use_existing) { + Log.d (TAG,"Generate new ecrytpion key of lenght = " + enc_len); + mEncryptionLength = enc_len; + if (mBroadcastState == BluetoothBroadcast.STATE_ENABLED || + mBroadcastState == BluetoothBroadcast.STATE_STREAMING) { + mEncKeyRefreshed = true; + Message msg = mHandler.obtainMessage(MSG_RESET_ENCRYPTION_FLAG_TIMEOUT); + mHandler.sendMessageDelayed(msg, 1000); + } + Message msg = mHandler.obtainMessage(MSG_SET_ENCRYPTION_KEY); + mHandler.sendMessage(msg); + } + return true; + } + + public byte[] GetEncryptionKey(String packageName) { + if (DBG) Log.d (TAG,"GetBroadcastEncryptionKey: package name = " + packageName); + + return BigBroadcastCode; + } + + public int GetBroadcastStatus(String packageName) { + if (DBG) Log.d (TAG,"GetBroadcastStatus: state = " + mBroadcastState + " package name = " + packageName); + return mBroadcastState; + } + + public boolean isBroadcastActive() { + if (mBroadcastDeviceIsActive == false) { + Log.d (TAG,"isBroadcastActive: Broadcast is turned to off"); + return false; + } + if (DBG) Log.d (TAG,"isBroadcastActive"); + return ((mBroadcastState == BluetoothBroadcast.STATE_ENABLED) || + (mBroadcastState == BluetoothBroadcast.STATE_STREAMING)); + } + + public BluetoothDevice getBroadcastDevice() { + if (DBG) Log.d (TAG,"getBroadcastDevice"); + return mBroadcastDevice; + } + + public String getBroadcastAddress() { + if (DBG) Log.d (TAG,"getBroadcastAddress"); + return mBroadcastAddress; + } + + public byte[] getBroadcastId() { + Log.d(TAG,"getBroadcastId: " + mBroadcastID); + return mBroadcastID; + } + + public boolean isBroadcastStreamingEncrypted() { + return mEncryptionEnabled; + } + + public boolean isBroadcastStreaming() { + return (mBroadcastState == BluetoothBroadcast.STATE_STREAMING); + } + + public String BroadcastGetAdvAddress() { + if (DBG) Log.d (TAG,"BroadcastGetAdvAddress: " + mAdvAddress); + return mAdvAddress; + } + + public int getNumSubGroups() { + if (DBG) Log.d (TAG,"getNumSubGroups: " + mNumSubGrps); + return mNumSubGrps; + } + + public int BroadcastGetAdvAddrType() { + return mAdvAddressType; + } + + public int BroadcatGetAdvHandle() { + //check if advertising + return mAdvertisingSet.getAdvertiserId(); + } + + public int BroadcastGetAdvInterval() { + return mPaInt; + } + public List BroadcastGetBisInfo() { + if (isBroadcastStreaming()) { + return mBisInfo; + } + Log.d(TAG,"BroadcastGetBisInfo: Broadcast is not active"); + return mBisInfo; + } + + public Map BroadcastGetMetaInfo() { + if (isBroadcastStreaming()) { + return mMetaInfo; + } + Log.d(TAG,"BroadcastGetMetaInfo: Broadcast is not active"); + return mMetaInfo; + } + public byte[] BroadcastGetMetadata() { + if (isBroadcastStreaming()) { + return mBroadcastBase.getMetadataContext(); + } + Log.d(TAG,"BroadcastGetMetadata: Broadcast is not active"); + return mBroadcastBase.getMetadataContext(); + } + public void setCodecPreference(String config_id, int ch_mode) { + if (isCodecConfigValid(config_id)) { + setCodecPreference(buildCodecConfig(config_id, ch_mode)); + } + } + public void setCodecPreference(BluetoothCodecConfig newConfig) { + if (DBG) Log.d (TAG, "setCodecPreference"); + if (newConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { + Log.e(TAG, "setCodecPreference: Invalid codec for broadcast mode: " + newConfig.getCodecType()); + return; + } + //mBroadcastCodecConfig.updateCodecConfig(newConfig); + if (mBroadcastState != BluetoothBroadcast.STATE_DISABLED) + mBroadcastNativeInterface.setCodecConfigPreference(mAdvertisingSet.getAdvertiserId(),newConfig); + } + + public void GetEncryptionKeyFromNative() { + Log.e(TAG,"GetEncryptionKeyFromNative"); + Message msg = mHandler.obtainMessage(MSG_GET_ENCRYPTION_KEY); + mHandler.sendMessage(msg); + } + private void setup_isodatapath(int adv_id, int big_handle,int num_bises, int[] bises) { + } + /* LE HAP broadcast hooks */ + public boolean startHAPBroadcast() { + if (isBroadcastActive()) { + //TODO: update codec config with HAP HQ mode + //Terminate BIG if created + //Notify codec config change to stack + //Create BIG and update BASE + } else { + //TODO: update codec config with HAP HQ mode + //Start Adv + //Existing encryption key will be used for HAP as only music streaming is supported + //Announcement content type will not be covered + } + return true; + } + public boolean stopHAPBroadcast() { + //TODO: DisableAudioPath + //Terminate BIG + //update state to disabling + //stop Adv + //reset codec config to default config + return true; + } + public void removeActiveDevice() { + if (DBG) Log.d (TAG,"removeActiveDevice"); + //int [] bis_handles = {-1, -1}; + if (mBroadcastDeviceIsActive == false) { + Log.d (TAG,"removeActiveDevice: mBADeviceIsActive is false, already removed"); + return; + } + mBroadcastDeviceIsActive = false; + synchronized (mBroadcastLock) { + if (mIsAdvertising && + (mBroadcastState == BluetoothBroadcast.STATE_ENABLED || + mBroadcastState == BluetoothBroadcast.STATE_STREAMING)) { + mBroadcastNativeInterface.disableBroadcast(mAdvertisingSet.getAdvertiserId()); + //mBroadcastAdvertiser.stopBroadcastAdvertising(); + } + if (!mBroadcastNativeInterface.setActiveDevice(false, mAdvertisingSet.getAdvertiserId())) { + Log.d(TAG,"SetActiveNative failed"); + } + } + //notifyBroadcastEnabled(false); + } + + public BluetoothCodecStatus getCodecStatus() { + if (DBG) Log.d (TAG,"getCodecStatus"); + BluetoothCodecConfig[] mBroadcastCodecConfig = {mCodecConfig}; + return (new BluetoothCodecStatus(mCodecConfig, mBroadcastCodecConfig, mBroadcastCodecConfig)); + } + public int setActiveDevice(BluetoothDevice device) { + if (DBG) Log.d (TAG,"setActiveDevice"); + if (device == null) { + removeActiveDevice(); + return ActiveDeviceManagerService.SHO_SUCCESS; + } + if (!Objects.equals(device, mBroadcastDevice)) { + Log.d(TAG,"setActiveDevice: Not a Broadcast device"); + return ActiveDeviceManagerService.SHO_FAILED; + } + if (!mBroadcastNativeInterface.setActiveDevice(true, mAdvertisingSet.getAdvertiserId())) { + Log.d(TAG,"SetActiveNative failed"); + return ActiveDeviceManagerService.SHO_FAILED; + } + mBroadcastDeviceIsActive = true; + + return ActiveDeviceManagerService.SHO_SUCCESS; + } + + public void notifyBroadcastEnabled(boolean enabled) { + if (DBG) Log.d (TAG,"notifyBroadcastEnabled: " + enabled); + ActiveDeviceManagerService activeDeviceManager = ActiveDeviceManagerService.get(); + if(activeDeviceManager == null) { + Log.e(TAG,"ActiveDeviceManagerService not started. Return"); + return; + } + if (enabled) + activeDeviceManager.enableBroadcast(mBroadcastDevice); + else + activeDeviceManager.disableBroadcast(); + } + + public void updateMetadataFromAvrcp(MediaMetadata data) { + if (DBG) Log.d (TAG,"updateMetadataFromAvrcp"); + mTrackMetadata = new TrackMetadata(data); + } + public void messageFromNative(BroadcastStackEvent event) { + if (DBG) Log.d (TAG,"messageFromNative: event " + event); + switch(event.type) { + case BroadcastStackEvent.EVENT_TYPE_BROADCAST_STATE_CHANGED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_BROADCAST_STATE, + event.valueInt, event.advHandle); + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_BROADCAST_AUDIO_STATE, + event.valueInt, event.advHandle); + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_CODEC_STATE); + msg.obj = event.codecStatus; + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_ENC_KEY_GENERATED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_ENCRYPTION_KEY); + msg.obj = event.key; + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_SETUP_BIG: + { + mBIGHandle = event.bigHandle; + if (event.valueInt == 1) + mNumBises = event.NumBises; + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_SETUP_BIG,event.valueInt, event.advHandle); + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_BROADCAST_ID_GENERATED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_BROADCAST_ID); + for (int i = 0; i < mBroadcastIdLength; i++) { + mBroadcastID[i] = (byte)event.BroadcastId[i]; + Log.d(TAG,"mBroadcastID["+i+"]" + " = " + mBroadcastID[i]); + } + mHandler.sendMessage(msg); + } + break; + default: + Log.e (TAG,"messageFromNative: Invalid"); + } + } + class TrackMetadata { + private String title; + private String artistName; + private String albumName; + private String genre; + private long playingTimeMs; + + public TrackMetadata(MediaMetadata data) { + if (data == null) return; + artistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST)); + albumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM)); + title = data.getString(MediaMetadata.METADATA_KEY_TITLE); + genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE)); + playingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION); + } + private String stringOrBlank(String s) { + return s == null ? new String() : s; + } + } + class BroadcastAdvertiser { + public BroadcastAdvertiser() { + Log.i(TAG,"BroadcastAdvertiser"); + mCallback = new BroadcastAdvertiserCallback(); + mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); + if (mAdvertiser == null) { + Log.e(TAG, "BroadcastAdvertiser: mAdvertiser is null"); + } + } + public void startBroadcastAdvertising() { + Log.i(TAG,"startBroadcastAdvertising"); + if (mAdvertiser == null) { + Log.e(TAG,"startBroadcastAdvertising: Advertiser is null"); + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + return; + } + AdvertisingSetParameters.Builder adv_param = + new AdvertisingSetParameters.Builder(); + adv_param.setLegacyMode(false); + adv_param.setConnectable(false); + adv_param.setScannable(false); + adv_param.setInterval(AdvertisingSetParameters.INTERVAL_MIN); //100msec + adv_param.setTxPowerLevel(mTxPowerLevel); + adv_param.setPrimaryPhy(1); + adv_param.setSecondaryPhy(mSecPhy); + AdvertiseData AdvData = new AdvertiseData.Builder() + .setIncludeDeviceName(true) + .addServiceData(new ParcelUuid(BROADCAST_AUDIO_UUID), mBroadcastID).build(); + PeriodicAdvertisingParameters.Builder periodic_param = new PeriodicAdvertisingParameters.Builder(); + periodic_param.setIncludeTxPower(true); + periodic_param.setInterval(mPaInt); + AdvertiseData PeriodicData = new AdvertiseData.Builder().addServiceData(new ParcelUuid(BASIC_AUDIO_UUID), new byte[0]).build(); + Log.i(TAG,"Calling startAdvertisingSet"); + mAdvertiser.startAdvertisingSet(adv_param.build(), AdvData, null, periodic_param.build(), PeriodicData, 0, 0, mCallback); + } + public void stopBroadcastAdvertising() { + Log.i(TAG,"stopBroadcastAdvertising"); + if (mAdvertiser != null) + mAdvertiser.stopAdvertisingSet(mCallback); + } + + public void updatePAwithBase() { + Log.i(TAG,"updatePAwithBase"); + AdvertiseData PeriodicData = new AdvertiseData.Builder().addServiceData(new ParcelUuid(BASIC_AUDIO_UUID), mBroadcastBase.getBroadcastBaseInfo()).build(); + mAdvertisingSet.setPeriodicAdvertisingData(PeriodicData); + } + } + + private class BroadcastAdvertiserCallback extends AdvertisingSetCallback { + @Override + public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, + int status) { + Log.i(TAG, "onAdvertisingSetStarted status " + status + + " advertisingSet: " + advertisingSet + " txPower " + txPower); + if (status != BluetoothGatt.GATT_SUCCESS) { + Log.e(TAG,"Failed to start Broadcast Advertisement"); + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState,prev_state); + } + if (status == BluetoothGatt.GATT_SUCCESS) { + mAdvertisingSet = advertisingSet; + mIsAdvertising = true; + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_ENABLED; + Log.i(TAG,"onAdvertisingSetStarted: adv_id = " + advertisingSet.getAdvertiserId() + "copied id = " + mAdvertisingSet.getAdvertiserId()); + broadcastState(mBroadcastState,prev_state); + if (mHandler.hasMessages(MSG_RESET_ENCRYPTION_FLAG_TIMEOUT)) { + Message msg = + mHandler.obtainMessage(MSG_SET_BROADCAST_ACTIVE); + mHandler.sendMessageDelayed(msg,600); + } else { + notifyBroadcastEnabled(true); + } + int mChMode = mCodecConfig.getChannelMode(); + switch (mChMode) { + case BluetoothCodecConfig.CHANNEL_MODE_MONO: + case BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO: + mNumBises = 1 * mNumSubGrps; + break; + case BluetoothCodecConfig.CHANNEL_MODE_STEREO: + mNumBises = 2 * mNumSubGrps; + break; + default: + Log.e(TAG,"channel mode unknown"); + } + mBroadcastBase.populateBase(); + mBroadcastAdvertiser.updatePAwithBase(); + mAdvertisingSet.getOwnAddress(); + } + } + + @Override + public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { + Log.i(TAG, "onAdvertisingSetStopped advertisingSet: " + advertisingSet); + mIsAdvertising = false; + int prev_state = mBroadcastState; + if (!goingDown && mBroadcastDeviceIsActive) { + Log.d(TAG,"onAdvertisingSetStopped: Unexpected Broadcast turn off"); + notifyBroadcastEnabled(false); + } + if (goingDown) { + Message msg = mHandler.obtainMessage(MSG_UPDATE_BROADCAST_STATE, + BluetoothBroadcast.STATE_DISABLING); + mHandler.sendMessageDelayed(msg,500); + goingDown = false; + } else { + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + } + } + + @Override + public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, + int status) { + Log.i(TAG, "onAdvertisingEnabled advertisingSet: " + advertisingSet + + " status " + status + " enable: " + enable); + } + + @Override + public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) { + Log.i(TAG, "onAdvertisingDataSet advertisingSet: " + advertisingSet + + " status " + status); + if (status == BluetoothGatt.GATT_SUCCESS) { + Log.i(TAG, "onAdvertisingDataSet: Base Info updated"); + } + } + @Override + public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, + int txPower, int status) { + Log.i(TAG, "onAdvertisingParametersUpdated advertisingSet: " + advertisingSet + + " status " + status + " txPower " + txPower); + } + + @Override + public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, + String address) { + Log.i(TAG, "onOwnAddressRead advertisingSet: " + advertisingSet + + " address " + address + " addressType " + addressType); + mAdvAddress = address; + mAdvAddressType = addressType; + } + + } + class BroadcastBase { + private final int LC3_SAMPLE_RATE_8000 = 0x01; + private final int LC3_SAMPLE_RATE_16000 = 0x02; + private final int LC3_SAMPLE_RATE_24000 = 0x03; + private final int LC3_SAMPLE_RATE_32000 = 0x04; + private final int LC3_SAMPLE_RATE_44100 = 0x05; + private final int LC3_SAMPLE_RATE_48000 = 0x06; + + int presentationDelay = 0x009C40; + byte [] mPresentationDelay = new byte[3]; + byte [] mCodecId = new byte[5]; + byte [] mCodecSpecificLength = new byte[1]; + byte [] mCodecSpecificSampleRate = new byte[3]; + byte [] mCodecSpecificFrameDuration = new byte[3]; + byte [] mCodecSpecificAudioLocation = new byte[6]; + byte [] mCodecSpecificOctetsPerFrame = new byte[3]; + byte [] mCodecSpecificBlocksPerSdu = new byte[3]; + byte [] mCodecSpecificLengthL2 = new byte[1]; + byte [] mCodecSpecificSampleRateL2 = new byte[3]; + byte [] mCodecSpecificFrameDurationL2 = new byte[3]; + byte [] mCodecSpecificAudioLocationL2 = new byte[6]; + byte [] mCodecSpecificOctetsPerFrameL2 = new byte[3]; + byte [] mCodecSpecificBlocksPerSduL2 = new byte[3]; + byte [] mMetadataLength = new byte[1]; + byte [] mMetadataContext = new byte[3]; + byte [] mNumSubgroups = new byte[1]; + byte [] mL2CodecID = new byte[1]; + byte [] mL2CodecSpecificLength = new byte[1]; + byte [] mL2mMetadataLength = new byte[1]; + byte [] mL2NumBises = new byte[1]; + byte [] mL2BisIndices = new byte[2]; + byte [] mL3BisIndex = new byte[1]; + byte [] mL3CodecSpecificLength = new byte[1]; + byte [] mL3CodecSpecificAudioLocation = new byte[6]; + byte mSampleRateLength = 2; + byte mSampleRateType = 0x01; + byte mFrameDurationLength = 2; + byte mFrameDurationType = 0x02; + byte mFrameDuration_7_5 = 0x00;//7.5 msec + byte mFrameDuration_10 = 0x01;//10msec + byte mAudioLocationLength = 5; + byte mAudioLocationType = 0x03; + byte mAudioLocationLeft = 0x01; + byte mAudioLocationRight = 0x02; + byte mAudioLocationCentre = 0x04; + byte mOctetsPerFrameLength = 3; + byte mOctestPerFrameType = 0x04; + byte mBlocksPerSduLength = 2; + byte mBlocksPerSduType = 0x05; + long LC3_CODEC_ID_OLD = 0x0000000001; + long LC3_CODEC_ID = 0x0000000006; + byte mCodecConfigLength = 0x10; //to be changed + byte mMediaContextType = 0x10; + byte [] BroadcastBaseArray = null; + //Metadata AD type + //Metadata + public BroadcastBase() { + //mccid = 0; + //int presentationDelay = 0x000014; + if (mPD == 20) { + Log.d(TAG,"Presentation Delay is set to 20msec"); + presentationDelay = 0x004E20; + } + if (mNewVersion) { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + mNumSubgroups[0] = (byte)mNumSubGrps; + } else { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + if (new_codec_id) { + mCodecId = longTobyteArray(LC3_CODEC_ID,5); + } else { + mCodecId = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mCodecSpecificLength[0] = mCodecConfigLength; + mCodecSpecificSampleRate = updateSampleRate(); + mCodecSpecificFrameDuration = updateFrameDuration(); + mCodecSpecificAudioLocation = updateAudioLocation(0); + mCodecSpecificOctetsPerFrame = updateOctetsPerFrame(); + mMetadataLength[0] = (byte)0x03; + int index = 0; + mMetadataContext[index++] = (byte)0x02; //length + mMetadataContext[index++] = (byte)mMediaContextType; //Type + mMetadataContext[index++] = (byte)0x01; //Value Music + mNumSubgroups[0] = (byte)mNumSubGrps; // only one set of broadcast is supported. + } + } + public byte [] getBroadcastBaseInfo() { + return BroadcastBaseArray; + } + public void updateBIGhandle(int handle) { + mBIGHandle = handle; + } + + public byte[] getMetadataContext() { + return mMetadataContext; + } + + public int getNumSubGroups() { + return mNumSubgroups[0]; + } + public byte [] updateSampleRate() { + int SR = mCodecConfig.getSampleRate(); + byte bytevalue; + switch (SR) { + case BluetoothCodecConfig.SAMPLE_RATE_48000: + if (mNewVersion) { + bytevalue = (byte)0x08; + } else { + bytevalue = (byte)0x06; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_44100: + if (mNewVersion) { + bytevalue = (byte)0x07; + } else { + bytevalue = (byte)0x05; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_32000: + if (mNewVersion) { + bytevalue = (byte)0x06; + } else { + bytevalue = (byte)0x04; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_24000: + if (mNewVersion) { + bytevalue = (byte)0x05; + } else { + bytevalue = (byte)0x03; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_16000: + if (mNewVersion) { + bytevalue = (byte)0x03; + } else { + bytevalue = (byte)0x02; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_8000: + bytevalue = (byte)0x01; + break; + default: + if (mNewVersion) { + bytevalue = (byte)0x08; + } else { + bytevalue = (byte)0x06; + } + } + byte [] ltv = {mSampleRateLength, mSampleRateType, bytevalue}; + return ltv; + } + public byte[] updateOctetsPerFrame() { + long bitrate = (int) mCodecConfig.getCodecSpecific1(); + long frameDuration = (int) mCodecConfig.getCodecSpecific2(); + byte bytevalue; + //Update OctetsPerFrame based on frame duration + switch ((int)bitrate) { + case 1001: + if (frameDuration == 0) { //7.5msec + bytevalue = (byte)30; + } else { //10msec + bytevalue = (byte)40; + } + break; + case 1002: + if (frameDuration == 0) { + bytevalue = (byte)45; + } else { + bytevalue = (byte)60; + } + break; + case 1004: + if (frameDuration == 0) { + bytevalue = (byte)75; + } else { + bytevalue = (byte)100; + } + break; + case 1006: + if (frameDuration == 0) { + bytevalue = (byte)90; + } else { + bytevalue = (byte)120; + } + break; + case 1007: + if (frameDuration == 0) { + bytevalue = (byte)117; + } else { + bytevalue = (byte)155; + } + break; + default: + bytevalue = (byte)100; + } + Log.d(TAG,"updateOctetsPerFrame: " + bytevalue); + byte [] ltv = {mOctetsPerFrameLength, mOctestPerFrameType, bytevalue, 0x00}; + return ltv; + } + private byte[] updateBlocksPerSdu() { + byte[] ltv = {mBlocksPerSduLength, mBlocksPerSduType,0x01}; + return ltv; + } + + public byte [] updateHAPSampleRate() { + int SR = mCodecConfig.getSampleRate(); + byte bytevalue; + switch (SR) { + case BluetoothCodecConfig.SAMPLE_RATE_16000: + bytevalue = (byte)0x02; + break; + case BluetoothCodecConfig.SAMPLE_RATE_24000: + bytevalue = (byte)0x03; + default: + bytevalue = (byte)0x02; + } + byte[] ltv = {mSampleRateLength, mSampleRateType, bytevalue}; + return ltv; + } + public byte [] updateHapOctetsPerFrame() { + long bitrate = mCodecConfig.getCodecSpecific1(); + long frameDuration = (int) mCodecConfig.getCodecSpecific2(); + byte bytevalue; + //Update OctetsPerFrame based on frame duration + switch((int)bitrate) { + case 1001: + if (frameDuration == 0) { //7.5msec + bytevalue = (byte)30; + } else { //10msec + bytevalue = (byte)40; + } + break; + case 1002: + if (frameDuration == 0) { + bytevalue = (byte)45; + } else { + bytevalue = (byte)60; + } + break; + default: + bytevalue = (byte)40; + } + byte [] ltv = {mOctetsPerFrameLength, mOctestPerFrameType, bytevalue, 0x00}; + return ltv; + } + public byte [] updateAudioLocation(int bis_index) { + int ch_mode = mCodecConfig.getChannelMode(); + byte ch = 0; + if (bis_index == 0) { + // stereo + if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_STEREO || + ch_mode == BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO) + ch = (byte)0x03; + else if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_MONO) + ch = (byte)0x00; + } else { + if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_STEREO) { + int bises = (mNumBises/((int)mNumSubgroups[0])); + ch = (byte)(mAudioLocationRight - (bis_index % bises)); + } else if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO) { + ch = (byte)0x03; + } else if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_MONO) { + ch = (byte)0x00; + } + } + byte [] loc = {mAudioLocationLength, mAudioLocationType, ch, 0x00, 0x00, 0x00}; + return loc; + } + public byte[] updateFrameDuration() { + byte mFD = mFrameDuration_10; + if (mCodecConfig.getCodecSpecific2() == 0) { + Log.d(TAG,"updateFrameDuration: 7.5msec"); + mFD = mFrameDuration_7_5; + } else { + Log.d(TAG,"updateFrameDuration: 10 msec"); + } + byte[] ltv = {mFrameDurationLength,mFrameDurationType,mFD}; + return ltv; + } + public byte[] intTobyteArray(int intValue, int bytelen) { + byte [] val = new byte[bytelen]; + for (int i = 0; i < bytelen; i++) { + val[(bytelen - 1) -i] = (byte)((intValue >> (8 *(bytelen - (i + 1)))) & 0x000000FF); + } + return val; + } + public byte [] longTobyteArray(long longValue, int bytelen) { + byte [] val = new byte[bytelen]; + for (int i = 0; i < bytelen; i++) { + val[(bytelen - 1) -i] = (byte)((longValue >> (8 *(bytelen - (i + 1)))) & 0x00000000000000FF); + } + return val; + } + public int calculateBisPerGroup() { + int mChMode = mCodecConfig.getChannelMode(); + int numbis = 2; + switch (mChMode) { + case BluetoothCodecConfig.CHANNEL_MODE_MONO: + case BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO: + Log.d(TAG,"BisPerGroup is 1"); + numbis = 1; + break; + case BluetoothCodecConfig.CHANNEL_MODE_STEREO: + Log.d(TAG,"BisPerGroup is 2"); + numbis = 2; + break; + default: + Log.e(TAG,"channel mode unknown"); + } + return numbis; + } + public void populateBase() { + if (DBG) Log.d(TAG,"populateBase"); + byte [] baseL1 = populate_level1_base(); + byte [] baseL2 = populate_level2_base(); + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + ByteStr.write(baseL1, 0, baseL1.length); + ByteStr.write(baseL2, 0, baseL2.length); + if (!mNewVersion) { + byte [] baseL3 = populate_level3_base(); + ByteStr.write(baseL3, 0, baseL3.length); + } + BroadcastBaseArray = ByteStr.toByteArray(); + } + private byte [] populate_level1_base() { + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + if (mNewVersion) { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + mNumSubgroups[0] = (byte)mNumSubGrps;//calculate based on num bises and channel mode + ByteStr.write(mPresentationDelay, 0, mPresentationDelay.length); + ByteStr.write(mNumSubgroups, 0, mNumSubgroups.length); + } else { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + if (new_codec_id) { + mCodecId = longTobyteArray(LC3_CODEC_ID,5); + } else { + mCodecId = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mCodecSpecificLength[0] = mCodecConfigLength; + mCodecSpecificSampleRate = updateSampleRate(); + mCodecSpecificFrameDuration = updateFrameDuration(); + mCodecSpecificAudioLocation = updateAudioLocation(0); + mCodecSpecificOctetsPerFrame = updateOctetsPerFrame(); + mMetadataLength[0] = (byte)0x03; + byte [] mediacontext = {2, mMediaContextType, (byte)0x01}; + mNumSubgroups[0] = (byte)mNumSubGrps;//calculate based on num bises and channel mode + + ByteStr.write(mPresentationDelay, 0, mPresentationDelay.length); + ByteStr.write(mCodecId, 0, mCodecId.length); + ByteStr.write(mCodecSpecificLength, 0, mCodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRate, 0, mCodecSpecificSampleRate.length); + ByteStr.write(mCodecSpecificFrameDuration, 0, mCodecSpecificFrameDuration.length); + ByteStr.write(mCodecSpecificAudioLocation, 0, mCodecSpecificAudioLocation.length); + ByteStr.write(mCodecSpecificOctetsPerFrame, 0, mCodecSpecificOctetsPerFrame.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mMetadataContext, 0, mMetadataContext.length); + ByteStr.write(mNumSubgroups, 0, mNumSubgroups.length); + } + return ByteStr.toByteArray(); + } + private byte [] populate_level2_base() { + Log.d(TAG,"populate_level2_base, subgroup = " + mNumSubgroups[0]); + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + byte [] metalength = new byte[1]; + int bisPerGroup = calculateBisPerGroup();//mNumBises/mNumSubGrps; + byte [] numBises = new byte[1]; + numBises = intTobyteArray(bisPerGroup,1); + byte [] bisInd = new byte[bisPerGroup]; + if (mNewVersion) { + byte[] mcid = new byte[1]; + if (new_codec_id) { + mcid = longTobyteArray(LC3_CODEC_ID,5); + } else { + mcid = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mMetadataLength[0] = (byte)0x04; + byte [] mediacontext = {3, 2, (byte)0x04, (byte)0x00}; + int codecConfigLength = 0x13; + mCodecSpecificLength = intTobyteArray(codecConfigLength, 1); + for (int i = 0; i < mNumSubgroups[0]; i++) { + if (mPartialSimulcast) { + if (i < (mNumSubgroups[0] / 2)) { + //High quality + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(mcid, 0, mcid.length); + mCodecSpecificSampleRate = updateSampleRate(); + mCodecSpecificFrameDuration = updateFrameDuration(); + mCodecSpecificAudioLocation = updateAudioLocation(0); + mCodecSpecificOctetsPerFrame = updateOctetsPerFrame(); + mCodecSpecificBlocksPerSdu= updateBlocksPerSdu(); + ByteStr.write(mCodecSpecificLength, 0, mCodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRate, 0, mCodecSpecificSampleRate.length); + ByteStr.write(mCodecSpecificFrameDuration, 0, mCodecSpecificFrameDuration.length); + ByteStr.write(mCodecSpecificAudioLocation, 0, mCodecSpecificAudioLocation.length); + ByteStr.write(mCodecSpecificOctetsPerFrame, 0, mCodecSpecificOctetsPerFrame.length); + ByteStr.write(mCodecSpecificBlocksPerSdu, 0, mCodecSpecificBlocksPerSdu.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mediacontext, 0, mediacontext.length); + byte[] level3 = populate_level3_new_base(i, mcid, mCodecSpecificSampleRate, + mCodecSpecificFrameDuration, + mCodecSpecificOctetsPerFrame, + mCodecSpecificBlocksPerSdu, + mediacontext); + ByteStr.write(level3, 0, level3.length); + mMetaInfo.put(i,new MetadataLtv(mediacontext)); + } else { + //Low quality + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(mcid, 0, mcid.length); + mCodecSpecificSampleRateL2= updateHAPSampleRate(); + mCodecSpecificFrameDurationL2= updateFrameDuration(); + mCodecSpecificAudioLocationL2= updateAudioLocation(0); + mCodecSpecificOctetsPerFrameL2= updateHapOctetsPerFrame(); + mCodecSpecificBlocksPerSduL2= updateBlocksPerSdu(); + ByteStr.write(mCodecSpecificLength, 0, mCodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRateL2, 0, mCodecSpecificSampleRateL2.length); + ByteStr.write(mCodecSpecificFrameDurationL2, 0, mCodecSpecificFrameDurationL2.length); + ByteStr.write(mCodecSpecificAudioLocationL2, 0, mCodecSpecificAudioLocationL2.length); + ByteStr.write(mCodecSpecificOctetsPerFrameL2, 0, mCodecSpecificOctetsPerFrameL2.length); + ByteStr.write(mCodecSpecificBlocksPerSduL2, 0, mCodecSpecificBlocksPerSduL2.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mediacontext, 0, mediacontext.length); + byte[] level3 = populate_level3_new_base(i, mcid, mCodecSpecificSampleRateL2, + mCodecSpecificFrameDurationL2, + mCodecSpecificOctetsPerFrameL2, + mCodecSpecificBlocksPerSduL2, + mediacontext); + ByteStr.write(level3, 0, level3.length); + mMetaInfo.put(i,new MetadataLtv(mediacontext)); + } + } else { + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(mcid, 0, mcid.length); + mCodecSpecificLengthL2 = intTobyteArray(codecConfigLength, 1); + mCodecSpecificSampleRateL2 = updateSampleRate(); + mCodecSpecificFrameDurationL2 = updateFrameDuration(); + mCodecSpecificAudioLocationL2 = updateAudioLocation(0); + mCodecSpecificOctetsPerFrameL2 = updateOctetsPerFrame(); + mCodecSpecificBlocksPerSduL2 = updateBlocksPerSdu(); + + ByteStr.write(mCodecSpecificLengthL2, 0, mCodecSpecificLengthL2.length); + ByteStr.write(mCodecSpecificSampleRateL2, 0, mCodecSpecificSampleRateL2.length); + ByteStr.write(mCodecSpecificFrameDurationL2, 0, mCodecSpecificFrameDurationL2.length); + ByteStr.write(mCodecSpecificAudioLocationL2, 0, mCodecSpecificAudioLocationL2.length); + ByteStr.write(mCodecSpecificOctetsPerFrameL2, 0, mCodecSpecificOctetsPerFrameL2.length); + ByteStr.write(mCodecSpecificBlocksPerSduL2, 0, mCodecSpecificBlocksPerSduL2.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mediacontext, 0, mediacontext.length); + byte[] level3 = populate_level3_new_base(0, mcid, mCodecSpecificSampleRateL2, + mCodecSpecificFrameDurationL2, + mCodecSpecificOctetsPerFrameL2, + mCodecSpecificBlocksPerSduL2, + mediacontext); + ByteStr.write(level3, 0, level3.length); + mMetaInfo.put(i,new MetadataLtv(mediacontext)); + } + } + } else { + for (int i = 0; i < mNumSubgroups[0]; i++) { + if (mPartialSimulcast) { + if (i < (mNumSubgroups[0] / 2)) { + //High quality + byte[] mcid = new byte[1]; + mcid = intTobyteArray(0xFE,1); + mL2CodecSpecificLength = intTobyteArray(0,1);//(byte) 0; + ByteStr.write(mcid, 0, mcid.length); + ByteStr.write(mL2CodecSpecificLength, 0, mL2CodecSpecificLength.length); + } else { + //Low quality + byte[] mcid = new byte[5]; + if (new_codec_id) { + mcid = longTobyteArray(LC3_CODEC_ID,5); + } else { + mcid = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mCodecSpecificLengthL2 = intTobyteArray(mCodecConfigLength, 1); + mCodecSpecificSampleRateL2 = updateHAPSampleRate(); + mCodecSpecificFrameDurationL2 = updateFrameDuration(); + mCodecSpecificAudioLocationL2 = updateAudioLocation(0); + mCodecSpecificOctetsPerFrameL2 = updateHapOctetsPerFrame(); + ByteStr.write(mcid, 0, mcid.length); + ByteStr.write(mL2CodecSpecificLength, 0, mL2CodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRateL2, 0, mCodecSpecificSampleRateL2.length); + ByteStr.write(mCodecSpecificFrameDurationL2, 0, mCodecSpecificFrameDurationL2.length); + ByteStr.write(mCodecSpecificAudioLocationL2, 0, mCodecSpecificAudioLocationL2.length); + ByteStr.write(mCodecSpecificOctetsPerFrameL2, 0, mCodecSpecificOctetsPerFrameL2.length); + } + metalength = intTobyteArray(0, 1);//(byte)0; + for (int j = 0; j < bisPerGroup;j++) { + bisInd[j] = (byte)(1 + (bisPerGroup * i) + j); + } + ByteStr.write(metalength, 0, metalength.length); + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(bisInd, 0, bisInd.length); + } else { + byte [] mcid = new byte[1]; + mcid = intTobyteArray(0xFE,1);//(byte)0xFE; + mL2CodecSpecificLength = intTobyteArray(0,1);//(byte)0; + metalength = intTobyteArray(0,1);//(byte)0; + for (int j = 0; j < bisPerGroup;j++) { + bisInd[j] = (byte)(1 + (bisPerGroup * i) + j); + } + ByteStr.write(mcid, 0, mcid.length); + ByteStr.write(mL2CodecSpecificLength, 0, mL2CodecSpecificLength.length); + ByteStr.write(metalength, 0, metalength.length); + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(bisInd, 0, bisInd.length); + } + } + } + return ByteStr.toByteArray(); + } + private byte[] populate_level3_base() { + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + for (int i = 0; i < mNumBises; i++) { + byte[] index = new byte[1]; + byte [] configlength = new byte[1]; + index[0] = (byte)(1 + i); //fetch from mAdvertisingSet + configlength[0] = (byte)6; + byte [] config = updateAudioLocation(i+1); + ByteStr.write(index, 0, index.length); + ByteStr.write(configlength,0, configlength.length); + ByteStr.write(config, 0, config.length); + mBisInfo.add(new BisInfo((int)index[0], mCodecId, mCodecSpecificSampleRate, mCodecSpecificFrameDuration, + config, mCodecSpecificOctetsPerFrame, mMetadataContext)); + } + return ByteStr.toByteArray(); + } + private byte[] populate_level3_new_base(int subGroupId, byte[] codecId, byte[] SampleRate, + byte[] frameDuration, byte[] octetsPerFrame, byte[] BlocksPerSdu, + byte[] mMetadata) { + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + int bisPerGroup = calculateBisPerGroup(); + for (int i = 0; i < bisPerGroup; i++) { + byte[] index = new byte[1]; + byte [] configlength = new byte[1]; + index[0] = (byte)(1 + i + (bisPerGroup * subGroupId)); + configlength[0] = (byte)6; + byte [] config = updateAudioLocation(i+1); + ByteStr.write(index, 0, index.length); + ByteStr.write(configlength,0, configlength.length); + ByteStr.write(config, 0, config.length); + mBisInfo.add(new BisInfo((int)index[0], codecId, SampleRate, frameDuration, config, + octetsPerFrame, BlocksPerSdu, mMetadata, subGroupId)); + } + return ByteStr.toByteArray(); + } + } + public class BisInfo { + public int BisIndex; + public byte [] mCodecId = new byte[5]; + public CodecConfigLtv BisCodecConfig; + public MetadataLtv BisMetadata; + public int mSubGroupId; + public BisInfo(int index, byte[] codecId, byte[] CodecSpecificSampleRate, byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, byte[] CodecSpecificOctetsPerFrame, byte[] AudioContext) { + BisIndex = index; + mCodecId = codecId; + BisCodecConfig = new CodecConfigLtv(CodecSpecificSampleRate, CodecSpecificFrameDuration, + CodecSpecificAudioLocation, CodecSpecificOctetsPerFrame); + BisMetadata = new MetadataLtv(AudioContext); + mSubGroupId = -1; + } + public BisInfo (int index, byte[] codecId, byte[] CodecSpecificSampleRate, byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, byte[] CodecSpecificOctetsPerFrame, + byte[] CodecSpecificBlocksPerSdu, byte[] AudioContext, int subGroupId) { + BisIndex = index; + mCodecId = codecId; + BisCodecConfig = new CodecConfigLtv(CodecSpecificSampleRate, CodecSpecificFrameDuration, + CodecSpecificAudioLocation, CodecSpecificBlocksPerSdu, + CodecSpecificOctetsPerFrame); + BisMetadata = new MetadataLtv(AudioContext); + mSubGroupId = subGroupId; + } + } + public class CodecConfigLtv{ + byte [] mCodecSpecificSampleRate; + byte [] mCodecSpecificFrameDuration; + byte [] mCodecSpecificAudioLocation; + byte [] mCodecSpecificOctetsPerFrame; + byte [] mCodecSpecificBlocksPerSdu; + public CodecConfigLtv(byte[] CodecSpecificSampleRate, + byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, + byte[] CodecSpecificOctetsPerFrame) { + mCodecSpecificSampleRate = CodecSpecificSampleRate; + mCodecSpecificFrameDuration = CodecSpecificFrameDuration; + mCodecSpecificAudioLocation = CodecSpecificAudioLocation; + mCodecSpecificOctetsPerFrame = CodecSpecificOctetsPerFrame; + } + public CodecConfigLtv(byte[] CodecSpecificSampleRate, + byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, + byte[] CodecSpecificOctetsPerFrame, + byte [] CodecSpecificBlocksPerSdu) { + mCodecSpecificSampleRate = CodecSpecificSampleRate; + mCodecSpecificFrameDuration = CodecSpecificFrameDuration; + mCodecSpecificAudioLocation = CodecSpecificAudioLocation; + mCodecSpecificOctetsPerFrame = CodecSpecificOctetsPerFrame; + mCodecSpecificBlocksPerSdu = CodecSpecificBlocksPerSdu; + } + public byte[] getByteArray() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + outputStream.write(mCodecSpecificSampleRate); + outputStream.write(mCodecSpecificFrameDuration); + outputStream.write(mCodecSpecificAudioLocation); + outputStream.write(mCodecSpecificOctetsPerFrame); + if (mNewVersion) { + outputStream.write(mCodecSpecificBlocksPerSdu); + } + } catch (IOException e) { + Log.e(TAG, "getBytes: ioexception caught!" + e); + return null; + } + return outputStream.toByteArray( ); + } + } + public class MetadataLtv { + byte[] mAudioContext; + public MetadataLtv(byte[] audiocontext) { + mAudioContext = audiocontext; + } + public byte[] getByteArray() { + return mAudioContext; + } + } + class BroadcastCodecConfig { + public BroadcastCodecConfig() { + //Default configuration + int sr, ch_mode; + long codecspecific1; + switch(mBroadcastConfigSettings) { + case 1: + sr = BluetoothCodecConfig.SAMPLE_RATE_16000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1001;//32kbps + break; + case 2: + sr = BluetoothCodecConfig.SAMPLE_RATE_16000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1001;//32kbps + break; + case 3: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1004;//80kbps + break; + case 4: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1004;//80kbps + break; + case 5: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1006;//96kbps + break; + case 6: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1006;//96 + break; + case 7: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1007;//124 + break; + case 8: + default: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007; + break; + + } + mCodecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + sr, BluetoothCodecConfig.BITS_PER_SAMPLE_24, + ch_mode, codecspecific1, 1, 0, 0); + if (mPartialSimulcast) { + mHapCodecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_16000, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 1, 0, 0); + + } + } + public void updateBroadcastCodecConfig(BluetoothCodecConfig newConfig) { + if (DBG) Log.d(TAG, "updateBroadcastCodecConfig: " + newConfig); + mCodecConfig = newConfig; + int mChMode = mCodecConfig.getChannelMode(); + switch (mChMode) { + case BluetoothCodecConfig.CHANNEL_MODE_MONO: + case BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO: + mNumBises = 1 * mNumSubGrps; + break; + case BluetoothCodecConfig.CHANNEL_MODE_STEREO: + mNumBises = 2 * mNumSubGrps; + break; + default: + Log.e(TAG,"channel mode unknown"); + } + } + } + + /** + * Binder object: must be a static class or memory leak may occur. + */ + @VisibleForTesting + static class BluetoothBroadcastBinder extends IBluetoothBroadcast.Stub + implements IProfileServiceBinder { + private BroadcastService mService; + + private BroadcastService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + BluetoothBroadcastBinder(BroadcastService svc) { + mService = svc; + } + + @Override + public void cleanup() { + mService = null; + } + @Override + public boolean SetBroadcast(boolean enable, String packageName) { + BroadcastService service = getService(); + if (service == null) { + return false; + } + if (enable) { + return service.EnableBroadcast(packageName); + } + else { + return service.DisableBroadcast(packageName); + } + //return false; + } + + @Override + public boolean SetEncryption(boolean enable, int enc_len, boolean use_existing, + String packageName) { + BroadcastService service = getService(); + if (service == null) { + return false; + } + return service.SetEncryption(enable, enc_len, use_existing, packageName); + } + + @Override + public byte[] GetEncryptionKey(String packageName) { + BroadcastService service = getService(); + if (service == null) { + return null; + } + return service.GetEncryptionKey(packageName); + } + @Override + public int GetBroadcastStatus(String packageName) { + BroadcastService service = getService(); + if (service == null) { + return BluetoothBroadcast.STATE_DISABLED; + } + return service.GetBroadcastStatus(packageName); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..a1b5596d5af0b842ba04f80a5c96e85a89610eb8 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java @@ -0,0 +1,120 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package com.android.bluetooth.broadcast; + +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothBroadcast; +/** + * Stack event sent via a callback from JNI to Java, or generated. + */ +public class BroadcastStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_BROADCAST_STATE_CHANGED = 1; + public static final int EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED = 2; + public static final int EVENT_TYPE_ENC_KEY_GENERATED = 3; + public static final int EVENT_TYPE_CODEC_CONFIG_CHANGED = 4; + public static final int EVENT_TYPE_SETUP_BIG = 5; + public static final int EVENT_TYPE_BROADCAST_ID_GENERATED = 6; + + public static final int STATE_IDLE = 0; + public static final int STATE_CONFIGURED = 1; + public static final int STATE_STREAMING = 2; + + public static final int STATE_STOPPED = 0; + public static final int STATE_STARTED = 1; + + public int type = EVENT_TYPE_NONE; + public int advHandle = 0; + public int valueInt = 0; + public int bigHandle = 0; + public int NumBises = 0; + public int[] BisHandles; + public byte[] BroadcastId = new byte[3]; + public String key; + public BluetoothCodecStatus codecStatus; + + BroadcastStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("BroadcastStackEvent {type:" + eventTypeToString(type)); + result.append(", value1:" + eventTypeValueIntToString(type, valueInt)); + if (codecStatus != null) { + result.append(", codecStatus:" + codecStatus); + } + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_BROADCAST_STATE_CHANGED: + return "EVENT_TYPE_BROADCAST_STATE_CHANGED"; + case EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED: + return "EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED"; + case EVENT_TYPE_ENC_KEY_GENERATED: + return "EVENT_TYPE_ENC_KEY_GENERATED"; + case EVENT_TYPE_CODEC_CONFIG_CHANGED: + return "EVENT_TYPE_CODEC_CONFIG_CHANGED"; + case EVENT_TYPE_SETUP_BIG: + return "EVENT_TYPE_SETUP_BIG"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } + + private static String eventTypeValueIntToString(int type, int value) { + switch (type) { + case EVENT_TYPE_BROADCAST_STATE_CHANGED: + switch (value) { + case BluetoothBroadcast.STATE_DISABLED: + return "DISABLED"; + case BluetoothBroadcast.STATE_ENABLING: + return "ENABLING"; + case BluetoothBroadcast.STATE_ENABLED: + return "CONFIGURED"; + case BluetoothBroadcast.STATE_STREAMING: + return "STREAMING"; + default: + break; + } + break; + case EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED: + switch(value) { + case BluetoothBroadcast.STATE_PLAYING: + return "PLAYING"; + case BluetoothBroadcast.STATE_NOT_PLAYING: + return "NOT PLAYING"; + default: + break; + } + default: + break; + } + return Integer.toString(value); + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..eb4df0bcb2d21c94395def01daccdefc716c974a --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java @@ -0,0 +1,100 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.cc; + +/* + * @hide + */ +public final class CCHalConstants { + static final int NETWORK_STATE_NOT_AVAILABLE = 0; + static final int NETWORK_STATE_AVAILABLE = 1; + + static final int SERVICE_TYPE_HOME = 0; + static final int SERVICE_TYPE_ROAMING = 1; + + static final int CALL_STATE_ACTIVE = 0; + static final int CALL_STATE_HELD = 1; + static final int CALL_STATE_DIALING = 2; + static final int CALL_STATE_ALERTING = 3; + static final int CALL_STATE_INCOMING = 4; + static final int CALL_STATE_WAITING = 5; + static final int CALL_STATE_IDLE = 6; + static final int CALL_STATE_DISCONNECTED = 7; + + //Call State as expected by Stack/CC + static final int CCS_STATE_INCOMING = 0x00; + static final int CCS_STATE_DIALING = 0x01; + static final int CCS_STATE_ALERTING = 0x02; + static final int CCS_STATE_ACTIVE = 0x03; + static final int CCS_STATE_LOCAL_HELD= 0x04; + static final int CCS_STATE_REMOTELY_HELD= 0x05; + static final int CCS_STATE_LOCAL_REMOTE_HELD= 0x06; + static final int CCS_STATE_DISCONNECTED = 0x07; + + static final int BTCC_OP_ACCEPT = 0; + static final int BTCC_OP_TERMINATE = 1; + static final int BTCC_OP_LOCAL_HLD = 2; + static final int BTCC_OP_LOCAL_RETRIEVE = 3; + static final int BTCC_OP_ORIGINATE = 4; + static final int BTCC_OP_JOIN = 5; + + static final int BTCC_OP_SUCCESS = 0x00; + static final int BTCC_OP_NOT_POSSIBLE = 0x02; + + //default call index for failures + static final int BTCC_DEF_INDEX_FOR_FAILURES = 0; + + static int getCCsCallState(int telephonyCallState) { + int ret = 0xFF; + switch(telephonyCallState) { + case CALL_STATE_ACTIVE: ret = CCS_STATE_ACTIVE; break; + case CALL_STATE_HELD: ret = CCS_STATE_LOCAL_HELD; break; + case CALL_STATE_DIALING: ret = CCS_STATE_DIALING; break; + case CALL_STATE_ALERTING: ret = CCS_STATE_ALERTING; break; + case CALL_STATE_INCOMING: ret = CCS_STATE_INCOMING; break; + case CALL_STATE_DISCONNECTED: ret = CCS_STATE_DISCONNECTED; break; + //this means second Incoming call is waiting + case CALL_STATE_WAITING: ret = CCS_STATE_INCOMING; break; + default: break; + } + return ret; + } + + public static String operationToString(int what) { + switch (what) { + case BTCC_OP_ACCEPT : + return "BTCC_OP_ACCEPT"; + case BTCC_OP_TERMINATE : + return "BTCC_OP_TERMINATE"; + case BTCC_OP_LOCAL_HLD : + return "BTCC_OP_LOCAL_HLD"; + case BTCC_OP_LOCAL_RETRIEVE : + return "BTCC_OP_LOCAL_RETRIEVE"; + case BTCC_OP_ORIGINATE : + return "BTCC_OP_ORIGINATE"; + case BTCC_OP_JOIN : + return "BTCC_OP_JOIN"; + default: + break; + } + return Integer.toString(what); + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..1e93c62fc4a7814ddebe3ade3dce9f263e5b697f --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java @@ -0,0 +1,255 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Defines the native interface that is used by state machine/service to + * send or receive messages from the native stack. This file is registered + * for the native methods in the corresponding JNI C++ file. + */ +package com.android.bluetooth.cc; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; +import java.util.ArrayList; +import java.util.List; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import java.nio.charset.StandardCharsets; + +/** + * Ccp Native Interface to/from JNI. + */ +public class CCNativeInterface { + private static final String TAG = "CCNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + + @GuardedBy("INSTANCE_LOCK") + private static CCNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private CCNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.w(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * This class is a singleton because native library should only be loaded once + * + * @return default instance + */ + public static CCNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new CCNativeInterface(); + } + return sInstance; + } + } + + /** + * Initialize native stack + * + * @param ccsClients maximum number of CCS clients that can be connected simultaneously + * @param inbandRingingEnabled whether in-band ringing is enabled on this AG + */ + @VisibleForTesting + public void init(int maxCcsClients, boolean inbandRingingEnabled) { + initializeNative("00008fd1-0000-1000-8000-00805F9B34FB", maxCcsClients, inbandRingingEnabled); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + /** + * Disconnects Call control from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnect(BluetoothDevice device) { + return disconnectNative(getByteAddress(device)); + } + /** + * update CC optional supported feature + * @param feature + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean callControlOptionalFeatures(int feature) { + return callControlPointOpcodeSupportedNative(feature); + } + + /** + * Sets the CC call state + * @param state + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean callState(ArrayList callList) { + int len = callList.size(); + byte[] cStateListBytes = new byte[len*3]; + for (int i=0; i mCallStateList = null; + private HashMap mPrevCallStateList = null; + private Queue mLccTobeQueued = null; + private Queue mLccWaitForResponseQ = null; + + private static final int FLAGS_DIRECTION_BIT = 0x0001; + private static final int CC_SIGNAL_STRENGTH_FACTOR = 20; + + private static final int CC_CONTENT_CONTROL_ID = 77; + private static final int CC_OPTIONAL_LOCAL_HOLD_FEAT = 0x01; + private static final int CC_OPTIONAL_JOIN_FEAT = 0x02; + private static final int CALL_CONTROL_OPTIONAL_FEATURES = CC_OPTIONAL_LOCAL_HOLD_FEAT|CC_OPTIONAL_JOIN_FEAT; + //native event + static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + static final int EVENT_TYPE_CALL_CONTROL_POINT_CHANGED = 2; + //CC to JNI update + static final int UPDATE_BEARER_NAME = 3; + static final int UPDATE_BEARER_TECH = 4; + static final int UPDATE_STATUS_FLAGS = 5; + static final int UPDATE_SIGNAL_STRENGTH = 6; + static final int UPDATE_BEARERLIST_SUPPORTED = 7; + static final int UPDATE_CONTENT_CONTROL_ID = 8; + static final int UPDATE_CALL_STATE = 9; + static final int UPDATE_CALL_CONTROL_OPCODES_SUPPORTED = 10; + static final int UPDATE_CALL_CONTROL_RESPONSE = 11; + static final int UPDATE_INCOMING_CALL = 12; + static final int PROCESS_CALL_STATE = 13; + static final int PROCESS_PHONE_STATE_CHANGED = 14; + static final int ACTIVE_DEVICE_CHANGED = 15; + + @Override + protected IProfileServiceBinder initBinder() { + return new CcBinder(this); + } + + @Override + protected void create() { + Log.i(TAG, "create()"); + if (mCreated) { + throw new IllegalStateException("create() called twice"); + } + mCreated = true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + if (mNativeInterface != null) { + mNativeInterface.cleanup(); + } + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + if (sCCService != null) { + Log.w(TAG, "CCService is already running"); + return true; + } + if (DBG) { + Log.d(TAG, "Create CCService Instance"); + } + + mContext = this; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when CCService starts"); + mNativeInterface = Objects.requireNonNull(CCNativeInterface.getInstance(), + "CcNativeInterface cannot be null when CcService starts"); + // Step 2: Get maximum number of connected audio devices + mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); + Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); + + if (mHandler != null) { + mHandler = null; + } + HandlerThread thread = new HandlerThread("BluetoothCCSHandler"); + thread.start(); + Looper looper = thread.getLooper(); + mHandler = new CcsMessageHandler(looper); + //APM's CallControl and CallAudio initialization + CallControl.init(mContext); + mCallAudio = CallAudio.init(mContext); + mNativeInterface.init(mMaxConnectedAudioDevices,InBandRingtoneSupport); + Log.d(TAG, "cc native init done"); + IntentFilter filter = new IntentFilter(); + //mSystemInterface = HeadsetService.getSystemInterfaceObj(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + mContext.registerReceiver(mBondStateChangedReceiver, filter); + mCallStateList = new HashMap<> (); + mPrevCallStateList = new HashMap<> (); + mLccWaitForResponseQ = new LinkedList<> (); + mLccTobeQueued = new LinkedList<> (); + mActiveDevMgrService = ActiveDeviceManagerService.get(); + setCCService(this); + return true; + } + + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sCCService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + // Step 8: Mark service as stopped + setCCService(null); + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + mContext.unregisterReceiver(mBondStateChangedReceiver); + // Clear AdapterService + mAdapterService = null; + mMaxConnectedAudioDevices = 1; + mCallOriginatedDevice = null; + CallControl.listenForPhoneState(PhoneStateListener.LISTEN_NONE); + return true; + } + + private static synchronized void setCCService(CCService instance) { + if (DBG) { + Log.d(TAG, "setCCService(): set to: " + instance); + } + sCCService = instance; + } + + public static synchronized CCService getCCService() { + if (sCCService == null) { + Log.w(TAG, "getCCService(): service is null"); + return null; + } + return sCCService; + } + + public boolean updateBearerProviderName(String name) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_BEARER_NAME; + msg.obj = name; + mHandler.sendMessage(msg); + return true; + } + public boolean updateBearerProviderTechnology (int tech_type) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_BEARER_TECH; + msg.arg1 = tech_type; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateSignalStrength(int signal) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_SIGNAL_STRENGTH; + msg.arg1 = signal*CC_SIGNAL_STRENGTH_FACTOR; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateSupportedBearerList(String supportedBearers) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_BEARERLIST_SUPPORTED ; + msg.obj = supportedBearers; + mHandler.sendMessage(msg); + return true; + } + + public void updateOriginateResult(BluetoothDevice device, int event, int res) { + if (mCallOriginatedDevice == null || device != mCallOriginatedDevice) { + Log.e(TAG, "Originate resp ignored, as there is no Orginate req"); + return; + } + if (res != 1) { + mCallOriginatedDevice = null; + updateCallControlResponse(CCHalConstants.BTCC_OP_ORIGINATE, + CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_NOT_POSSIBLE, device); + } + } + + public boolean updateContentControlID(int ccid) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CONTENT_CONTROL_ID; + msg.arg1 = ccid; + mHandler.sendMessage(msg); + mCCId = ccid; + return true; + } + + public boolean updateStatusFlags(int statusFlags) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_STATUS_FLAGS; + msg.arg1 = statusFlags; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateCallControlOptionalFeatures(int feature) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CALL_CONTROL_OPCODES_SUPPORTED; + msg.arg1 = feature; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateCallControlResponse(int op, int index, int status, BluetoothDevice device) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CALL_CONTROL_RESPONSE ; + msg.arg1 = op; + msg.arg2 = index; + msg.obj = status; + mHandler.sendMessage(msg); + return true; + } + + private boolean updateIncomingCall(int index, String uri) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_INCOMING_CALL ; + msg.arg1 = index; + msg.obj = uri; + mHandler.sendMessage(msg); + return true; + } + + boolean isVirtualCallStarted() { + + return mVirtualCallStarted; + } + + public void setVirtualCallActive(boolean state) { + Log.i(TAG, "setVirtualCallActive: " + state); + if (state == true) { + startScoUsingVirtualVoiceCall(); + } else { + stopScoUsingVirtualVoiceCall(); + } + } + + private void disaptchFakeCallState (CallControlState state) { + if (state != null) { + mCallStateList.put(state.mIndex, state); + } + Message msg = mHandler.obtainMessage(); + msg.what = PROCESS_CALL_STATE; + Collection values = mCallStateList.values(); + ArrayList listOfValues = new ArrayList<>(values); + msg.obj = listOfValues; + mHandler.sendMessage(msg); + } + + boolean startScoUsingVirtualVoiceCall() { + + Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString()); + mVirtualCallStarted = true; + // Send fake call states to mimic outgoing calls + + mCallStateList.clear(); + CallControlState alertingState = new CallControlState(1,CCHalConstants.CALL_STATE_ALERTING,FLAGS_DIRECTION_BIT); + disaptchFakeCallState(alertingState); + CallControlState activeState = new CallControlState(1,CCHalConstants.CALL_STATE_ACTIVE,FLAGS_DIRECTION_BIT); + disaptchFakeCallState(activeState); + return true; + } + + boolean stopScoUsingVirtualVoiceCall() { + + Log.i(TAG, "stopScoUsingVirtualVoiceCall: " + Utils.getUidPidString()); + // 1. Check if virtual call has already started + if (!mVirtualCallStarted) { + Log.w(TAG, "stopScoUsingVirtualVoiceCall: virtual call not started"); + return false; + } + mVirtualCallStarted = false; + // 2. Send fake call states to mimic it ias outgoing calls + + mCallStateList.clear(); + CallControlState disConnectedState = new CallControlState(1,CCHalConstants.CCS_STATE_DISCONNECTED,FLAGS_DIRECTION_BIT); + disaptchFakeCallState(disConnectedState); + return true; + } + + private void updateCallState(ArrayList listOfValues) { + Log.d(TAG, "updateCallState"); + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CALL_STATE; + msg.obj = listOfValues; + mHandler.sendMessage(msg); + } + + public void processAndUpdateCallState(ArrayList listOfValues) { + int flags = 0; + + for (CallControlState state : listOfValues) { + Log.i(TAG, "processAndUpdateCallState: direction" + state.mDirection); + if (state.mDirection == 1) { + //Incoming call: off the direction bit + flags = (flags & (~FLAGS_DIRECTION_BIT)); + } else { + //Outgoing call: on the direction bit + flags = (flags | FLAGS_DIRECTION_BIT); + } + state.mFlags = flags; + String uri = ""; + String uri_str = "tel:"; + Log.i(TAG, "processAndUpdateCallState: index = " + state.mIndex); + if (state.mState == CCHalConstants.CALL_STATE_ACTIVE) { + mLatestActiveCallIndex = state.mIndex; + } else if (state.mState == CCHalConstants.CALL_STATE_HELD) { + mLatestHeldCallIndex = state.mIndex; + } + if (state.mState == CCHalConstants.CALL_STATE_INCOMING) { + if (state.mNumber != null) { + uri = uri_str.concat(state.mNumber); + } + Log.i(TAG, "processAndUpdateCallState: inc uri = " + uri); + updateIncomingCall(state.mIndex, uri); + } + } + updateCallState(listOfValues); + } + + private void compareAndUpdateWithPrevCallList (HashMap currentCallStateList) { + Log.d(TAG, "compareAndUpdateWithPrevCallList"); + for (Integer key: mPrevCallStateList.keySet()) { + if (currentCallStateList.containsKey(key) == false) { + //create a fake disconnected for that index + if (mPrevCallStateList.get(key).mState != CCHalConstants.CALL_STATE_DISCONNECTED) { + Log.d(TAG, "inserting DISC state fake!"); + CallControlState fakeDiscForDisappeared = + new CallControlState(key,CCHalConstants.CALL_STATE_DISCONNECTED, mPrevCallStateList.get(key).mFlags); + mCallStateList.put(key, fakeDiscForDisappeared); + } + } + } + mPrevCallStateList.putAll(mCallStateList); + } + + public void clccResponse(int index, int direction, int call_status, int mode, boolean mpty, + String number, int type) { + Log.d(TAG, "clccResponse"); + if (index != 0) { + CallControlState state = new CallControlState(index, direction, call_status, number); + mCallStateList.put(index, state); + } else { + //update the call state to stack as 0 indicates end of call list + compareAndUpdateWithPrevCallList(mCallStateList); + Message msg = mHandler.obtainMessage(); + msg.what = PROCESS_CALL_STATE; + Collection values = mCallStateList.values(); + ArrayList listOfValues = new ArrayList<>(values); + msg.obj = listOfValues; + mHandler.sendMessage(msg); + if (!mLccWaitForResponseQ.isEmpty()) { + mLccWaitForResponseQ.remove(); + } + if (!mLccTobeQueued.isEmpty()) { + mLccTobeQueued.remove(); + getBlcc(); + } + } + } + + private void getBlcc() { + Log.d(TAG, "getBlcc"); + if (mLccTobeQueued.isEmpty()) { + if (CallControl.listCurrentCalls() == true) { + mLccWaitForResponseQ.add(1); + Log.d(TAG, "getBlcc: successfully sent"); + //telephony should always respond with clccresponse + mCallStateList.clear(); + } + } else { + mLccTobeQueued.add(1); + } + } + + private boolean processCallStateChange(CallControlState state) { + Message msg = mHandler.obtainMessage(); + msg.what = PROCESS_PHONE_STATE_CHANGED; + msg.obj = state; + mHandler.sendMessage(msg); + return true; + } + + boolean isInbandRingingEnabled() { + boolean returnVal; + + returnVal = BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean( + DISABLE_INBAND_RINGING_PROPERTY, true); + Log.d(TAG, "isInbandRingingEnabled returning: " + returnVal); + return returnVal; + } + + boolean isCallAudioNeeded(CallControlState state) { + boolean ret = false; + if (isInbandRingingEnabled() && state.mState == CCHalConstants.CALL_STATE_INCOMING) { + ret = true; + } else if (mCallAudio != null && mCallAudio.isAudioOn() == false && + (state.mState == CCHalConstants.CALL_STATE_ALERTING || + mPrevTelephonyState != null && mPrevTelephonyState.mNumActive == 0 && + state.mNumActive == 1)) { + + ret = true; + } + return ret; + } + + public boolean phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, + String name, boolean isVirtualCall) { + Log.d(TAG, "phoneStateChanged: " + + "callState: " + callState + + "number:" + number + + "numActive:" + numActive + + "isVirtualCall:" + isVirtualCall); + CallControlState currentTelephonyState = new CallControlState(numActive, numHeld,callState, number, type, name); + + if (isCallAudioNeeded(currentTelephonyState)) { + if (mCallAudio != null) { + mCallAudio.connectAudio(); + } else { + Log.e(TAG, "no CallAudio handle"); + } + } + + if (mPrevTelephonyState != null && mPrevTelephonyState.mNumActive == 1 + && currentTelephonyState.mNumActive == 0 && currentTelephonyState.mNumHeld == 0) { + if (mPrevTelephonyState.mNumHeld == 0 && currentTelephonyState.mNumHeld == 1) { + Log.d(TAG, "special case where Active call moved to HOLD"); + } else { + if (mCallAudio != null) { + mCallAudio.disconnectAudio(); + } else { + Log.e(TAG, "no CallAudio handle for disc Call handling"); + } + } + } + + if (callState == CCHalConstants.CALL_STATE_DIALING) { + //ignore this as it is fake Telephony event + return true; + } + + // Should stop all other audio mode in this case + if ((numActive + numHeld) > 0 || callState != CCHalConstants.CALL_STATE_IDLE) { + if (!isVirtualCall && mVirtualCallStarted) { + // stop virtual voice call if there is an incoming Telecom call update + stopScoUsingVirtualVoiceCall(); + } + processCallStateChange(currentTelephonyState); + mPrevTelephonyState = currentTelephonyState; + } else { + // ignore CS non-call state update when virtual call started + if (!isVirtualCall && mVirtualCallStarted) { + Log.i(TAG, "Ignore CS non-call state update"); + return true; + } + } + return true; + } + + public BluetoothDevice getActiveDevice() { + return mActiveDevice; + } + + public int getContentControlID() { + return mCCId; + } + + public boolean setActiveDevice(BluetoothDevice device) { + Message msg = mHandler.obtainMessage(); + msg.what = ACTIVE_DEVICE_CHANGED; + msg.obj = device; + mHandler.sendMessage(msg); + return true; + } + + private boolean setActiveDeviceRemoteTrigger(BluetoothDevice device) { + boolean ret = false; + if (mActiveDevMgrService != null) { + ret = mActiveDevMgrService.setActiveDeviceBlocking(device, ApmConst.AudioFeatures.CALL_AUDIO); + } + Log.d(TAG, "setActiveDevice returns" + ret); + return ret; + } + + private boolean isActiveDevice(BluetoothDevice device) { + boolean ret = false; + if (mActiveDevMgrService != null) { + ret = (device == mActiveDevMgrService.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO)); + } + Log.d(TAG, "isActiveDevice returns" + ret); + return ret; + } + + public boolean onCallControlPointChangedRequest(int op, int[] call_indices, int count, String dialNumber, BluetoothDevice device ) { + Log.d(TAG, " onCallControlPointChangedRequest opcode : " + CCHalConstants.operationToString(op)) ; + switch(op) { + case CCHalConstants.BTCC_OP_ACCEPT: { + setActiveDeviceRemoteTrigger (device); + CallControl.answerCall(device); + break; + } + case CCHalConstants.BTCC_OP_TERMINATE: { + int callIndex = call_indices[0]; + Log.d(TAG, "callIndex: " + callIndex); + CallControl.terminateCall(device, callIndex); + break; + } + case CCHalConstants.BTCC_OP_LOCAL_HLD:{ + int callIndex = call_indices[0]; + int res; + int idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + Log.d(TAG, "callIndex: " + callIndex); + if (CallControl.holdCall(device, callIndex) == true) { + res = CCHalConstants.BTCC_OP_SUCCESS; + idx = callIndex; + } else { + res = CCHalConstants.BTCC_OP_NOT_POSSIBLE; + idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + } + updateCallControlResponse(op, idx, res, device); + break; + } + case CCHalConstants.BTCC_OP_LOCAL_RETRIEVE: { + //Analogus to SWAP as stack would have + //already validated the input index is in HELD state + int chld = 2; + int res; + int idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + if (CallControl.processChld(device, chld) == true) { + res = CCHalConstants.BTCC_OP_SUCCESS; + idx = call_indices[0]; + } else { + res = CCHalConstants.BTCC_OP_NOT_POSSIBLE; + idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + } + updateCallControlResponse(op, idx, res, device); + break; + } + case CCHalConstants.BTCC_OP_ORIGINATE: { + Log.d(TAG, "Orignate: from Device: " + device + "dialString: " + dialNumber); + if (dialNumber == null) { + Log.e(TAG, "null dial string"); + break; + } + if (mCallOriginatedDevice != null) { + Log.d(TAG, "Originate is pending from device: " + mCallOriginatedDevice); + updateCallControlResponse(op, CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_NOT_POSSIBLE, device); + break; + } else { + setActiveDeviceRemoteTrigger (device); + String[] result = dialNumber.split(":"); + if (CallControl.dialOutgoingCall(device, result[1]) == true) { + mCallOriginatedDevice = device; + } else { + updateCallControlResponse(op, CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_NOT_POSSIBLE, device); + } + } + break; + } + case CCHalConstants.BTCC_OP_JOIN: { + //Stack would have validate to ensure the input indicies + //are valid candidates for JOIN op + int chld = 3; + int res; + int idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + if (CallControl.processChld(device, chld) == true) { + res = CCHalConstants.BTCC_OP_SUCCESS; + idx = call_indices[0]; + } else { + res = CCHalConstants.BTCC_OP_NOT_POSSIBLE; + idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + } + updateCallControlResponse(op, idx, res, device); + break; + } + } + return true; + } + + public void onCallControlInitialized(int status) { + Log.v(TAG, "CallControlInitializedCallback: status=" + status); + if (status == 0) { + //Initialize Telephony and APM related Initialization + CallControl.listenForPhoneState(PhoneStateListener.LISTEN_SERVICE_STATE|PhoneStateListener.LISTEN_SERVICE_STATE); + updateContentControlID(CC_CONTENT_CONTROL_ID); + updateSupportedBearerList("tel"); + updateCallControlOptionalFeatures(CALL_CONTROL_OPTIONAL_FEATURES); + } + } + + + public void onConnectionStateChanged(BluetoothDevice device, int status) { + Log.v(TAG, "onConnectionStateChanged: address=" + device.toString()); + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + + } + + private boolean callListContainsDialingCall(ArrayList listOfValues) { + boolean ret = false; + for (CallControlState state : listOfValues) { + if (state.mState == CCHalConstants.CALL_STATE_DIALING + || state.mState == CCHalConstants.CALL_STATE_ALERTING) { + ret = true; + break; + } + } + return ret; + } + + /** Handles CCS messages. */ + private final class CcsMessageHandler extends Handler { + private CcsMessageHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + if (DBG) Log.v(TAG, "CcsMessageHandler: received message=" + messageWhatToString(msg.what)); + ArrayList listOfValues = null; + switch (msg.what) { + case UPDATE_BEARER_NAME: + String bName = (String)msg.obj; + mNativeInterface.updateBearerProviderName(bName); + break; + case UPDATE_BEARER_TECH: + int tech_type = (int)msg.arg1; + mNativeInterface.updateBearerTechnology(tech_type); + break; + case UPDATE_SIGNAL_STRENGTH: + int signal = (int)msg.arg1; + mNativeInterface.updateSignalStrength(signal); + break; + case UPDATE_STATUS_FLAGS: + int statusFlags = (int)msg.arg1; + mNativeInterface.updateStatusFlags(statusFlags); + break; + case UPDATE_BEARERLIST_SUPPORTED : + String bSList = (String)msg.obj; + mNativeInterface.updateSupportedBearerList(bSList); + break; + case UPDATE_CONTENT_CONTROL_ID: + int ccid = (int)msg.arg1; + mNativeInterface.contentControlId(ccid); + break; + case UPDATE_CALL_STATE: + listOfValues = (ArrayList)msg.obj; + Log.d(TAG, "Call list size : " + listOfValues.size()); + boolean status = mNativeInterface.callState(listOfValues); + if (mCallOriginatedDevice != null && callListContainsDialingCall(listOfValues)) { + Log.e(TAG, "push the pending Originate response"); + //Stack will pick the right index + updateCallControlResponse(CCHalConstants.BTCC_OP_ORIGINATE, + CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_SUCCESS, mCallOriginatedDevice); + mCallOriginatedDevice = null; + } + break; + case UPDATE_CALL_CONTROL_OPCODES_SUPPORTED : + int feature = (int)msg.arg1; + mNativeInterface.callControlOptionalFeatures(feature); + break; + case UPDATE_CALL_CONTROL_RESPONSE : + int op = (int)msg.arg1; + int ind = (int)msg.arg2; + int st = (int)msg.obj; + mNativeInterface.callControlResponse(op, ind, st, null); + break; + case UPDATE_INCOMING_CALL : + int index = (int)msg.arg1; + String uri = (String)msg.obj; + mNativeInterface.updateIncomingCall(index, uri); + break; + case PROCESS_PHONE_STATE_CHANGED: + getBlcc(); + break; + case PROCESS_CALL_STATE: + listOfValues = (ArrayList)msg.obj; + processAndUpdateCallState(listOfValues); + break; + case ACTIVE_DEVICE_CHANGED: + BluetoothDevice device = (BluetoothDevice)msg.obj; + mNativeInterface.setActiveDevice(device,-1); + break; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + break; + default: + Log.e(TAG, "unknown message! msg.what=" + messageWhatToString(msg.what)); + break; + } + Log.v(TAG, "Exit handleMessage"); + } +} + + public static String messageWhatToString(int what) { + switch (what) { + case UPDATE_BEARER_NAME : + return "UPDATE_BEARER_NAME"; + case UPDATE_BEARER_TECH : + return "UPDATE_BEARER_TECH"; + case UPDATE_SIGNAL_STRENGTH : + return "UPDATE_SIGNAL_STRENGTH"; + case UPDATE_BEARERLIST_SUPPORTED : + return "UPDATE_BEARERLIST_SUPPORTED"; + case UPDATE_CONTENT_CONTROL_ID : + return "UPDATE_CONTENT_CONTROL_ID"; + case UPDATE_CALL_STATE : + return "UPDATE_CALL_STATE"; + case UPDATE_CALL_CONTROL_OPCODES_SUPPORTED : + return "UPDATE_CALL_CONTROL_OPCODES_SUPPORTED "; + case UPDATE_CALL_CONTROL_RESPONSE : + return "UPDATE_CALL_CONTROL_RESPONSE"; + case UPDATE_INCOMING_CALL : + return "UPDATE_INCOMING_CALL"; + case PROCESS_CALL_STATE : + return "PROCESS_CALL_STATE"; + case UPDATE_STATUS_FLAGS: + return "UPDATE_STATUS_FLAGS"; + default: + break; + } + return Integer.toString(what); + } + + /** + * Binder object: must be a static class or memory leak may occur. + */ + + static class CcBinder extends Binder implements IProfileServiceBinder { + private CCService mService; + + private CCService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + CcBinder(CCService svc) { + mService = svc; + } + + @Override + public void cleanup() { + mService = null; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java new file mode 100644 index 0000000000000000000000000000000000000000..2feb65a971a5f683b6b64c1ed1bbb57adce5f5ec --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java @@ -0,0 +1,84 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ +/* + * Copyright 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.bluetooth.cc; + +import java.util.Objects; +import java.util.Arrays; + +/** + * A blob of data representing an overall call state on the phone + */ +class CallControlState { + + int mIndex; + /** + * Number of active calls + */ + int mNumActive; + /** + * Number of held calls + */ + int mNumHeld; + /** + * Current call setup state + */ + int mState; + /** + * Currently active call's phone number + */ + String mNumber; + /** + * Phone number type + */ + int mType; + + /** + * flags to define direction, information witheld by network or server. + */ + int mFlags; + + /** + * Caller display name + */ + String mName; + + int mDirection; + + CallControlState(int numActive, int numHeld, int callState, String number, int type, + String name) { + mNumActive = numActive; + mNumHeld = numHeld; + mState = callState; + mNumber = number; + mType = type; + mName = name; + } + CallControlState(int index, int callState, int flags) { + mIndex = index; + mState = callState; + mFlags = flags; + } + CallControlState(int index, int direction, int callState, String number) { + mIndex = index; + mDirection = direction; + mState = callState; + mNumber = number; + } + +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java new file mode 100644 index 0000000000000000000000000000000000000000..13375e49f816f381997dfe106716a406a01a7318 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java @@ -0,0 +1,178 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package com.android.bluetooth.groupclient; + +import android.bluetooth.IBluetoothGroupCallback; +import android.bluetooth.BluetoothGroupCallback; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.UUID; + +/* This class keeps track of registered GroupClient applications and + * managing callbacks to be given to appropriate app or module */ + +public class GroupAppMap { + + private static final String TAG = "BluetoothGroupAppMap"; + + class GroupClientApp { + /* The UUID of the application */ + public UUID uuid; + + /* The id of the application */ + public int appId; + + /* flag to determine if Bluetooth module has registered. */ + public boolean isLocal; + + /* Callbacks to be given to application */ + public IBluetoothGroupCallback appCb; + + /* Callbacks to be given to registered Bluetooth modules*/ + public BluetoothGroupCallback mCallback; + + public boolean isRegistered; + + /** Death receipient */ + private IBinder.DeathRecipient mDeathRecipient; + + GroupClientApp(UUID uuid, boolean isLocal, IBluetoothGroupCallback appCb, + BluetoothGroupCallback localCallbacks) { + this.uuid = uuid; + this.isLocal = isLocal; + this.appCb = appCb; + this.mCallback = localCallbacks; + this.isRegistered = true; + appUuids.add(uuid); + } + + /** + * To link death recipient + */ + void linkToDeath(IBinder.DeathRecipient deathRecipient) { + try { + IBinder binder = ((IInterface) appCb).asBinder(); + binder.linkToDeath(deathRecipient, 0); + mDeathRecipient = deathRecipient; + } catch (RemoteException e) { + Log.e(TAG, "Unable to link deathRecipient for appId: " + appId); + } + } + + } + + List mApps = Collections.synchronizedList(new ArrayList()); + + ArrayList appUuids = new ArrayList(); + + /** + * Add an entry to the application list. + */ + GroupClientApp add(UUID uuid, boolean isLocal, IBluetoothGroupCallback appCb, + BluetoothGroupCallback localCallback) { + synchronized (mApps) { + GroupClientApp app = new GroupClientApp(uuid, isLocal, appCb, localCallback); + mApps.add(app); + return app; + } + } + + /** + * Remove the entry for a given UUID + */ + void remove(UUID uuid) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.uuid.equals(uuid)) { + entry.isRegistered = false; + i.remove(); + break; + } + } + } + } + + /** + * Remove the entry for a given application ID. + */ + void remove(int appId) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.appId == appId) { + entry.isRegistered = false; + i.remove(); + break; + } + } + } + } + + /** + * Get GroupClient application by UUID. + */ + GroupClientApp getByUuid(UUID uuid) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.uuid.equals(uuid)) { + return entry; + } + } + } + Log.e(TAG, "App not found for UUID " + uuid); + return null; + } + + /** + * Get a GroupClient application by appId. + */ + GroupClientApp getById(int appId) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.appId == appId) { + return entry; + } + } + } + Log.e(TAG, "GroupClient App not found for appId " + appId); + return null; + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..ce1076f480a4c362a80402310cc637dff794b19e --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java @@ -0,0 +1,251 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package com.android.bluetooth.groupclient; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import java.util.ArrayList; +import java.util.List; +import android.util.Log; +import java.util.UUID; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * CSIP Client Native Interface to/from JNI. + */ +public class GroupClientNativeInterface { + private static final String TAG = "BluetoothGroupNativeIntf"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + + @GuardedBy("INSTANCE_LOCK") + private static GroupClientNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private GroupClientNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtfStack(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static GroupClientNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new GroupClientNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + /** + * Register CSIP app with the stack code. + * + * @param appUuidLsb lsb of app uuid. + * @param appUuidMsb msb of app uuid. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void registerCsipApp(long appUuidLsb, long appUuidMsb) { + registerCsipAppNative(appUuidLsb, appUuidMsb); + } + + /** + * Register CSIP app with the stack code. + * + * @param appId ID of the application to be unregistered. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void unregisterCsipApp(int appId) { + unregisterCsipAppNative(appId); + } + + /** + * Change lock value of the coordinated set member + * + * @param appId ID of the application which is requesting change in lock status + * @param setId Identifier of the set + * @param devices List of bluetooth devices for whick lock status change is required + * @param value Lock/Unlock value + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setLockValue(int appId, int setId, List devices, + int value) { + int i = 0; + int size = ((devices != null) ? devices.size() : 0); + String[] devicesList = new String[size]; + if (size > 0) { + for (BluetoothDevice device: devices) { + devicesList[i++] = device.toString(); + } + } + setLockValueNative(appId, setId, value, devicesList); + } + + /** + * Initiates Csip connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean connectSetDevice(int appId, BluetoothDevice device) { + return connectSetDeviceNative(appId, getByteAddress(device)); + } + + /** + * Disconnects Csip from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectSetDevice(int appId, BluetoothDevice device) { + return disconnectSetDeviceNative(appId, getByteAddress(device)); + } + + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + private void onCsipAppRegistered (int status, int appId, + long uuidLsb, long uuidMsb) { + UUID uuid = new UUID(uuidMsb, uuidLsb); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onCsipAppRegistered(status, appId, uuid); + } + } + + private void onConnectionStateChanged(int appId, String bdAddr, + int state, int status) { + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice(bdAddr); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onConnectionStateChanged (appId, device, state, status); + } + } + + private void onNewSetFound (int setId, String bdAddr, int size, byte[] sirk, + long uuidLsb, long uuidMsb, boolean lockSupport) { + UUID uuid = new UUID(uuidMsb, uuidLsb); + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice(bdAddr); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onNewSetFound(setId, device, size, sirk, uuid, lockSupport); + } + } + + private void onNewSetMemberFound (int setId, String bdAddr) { + BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdAddr); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onNewSetMemberFound(setId, device); + } + } + + private void onLockStatusChanged (int appId, int setId, int value, + int status, String[] bdAddr) { + List lockMembers = new ArrayList(); + for (String address: bdAddr) { + lockMembers.add(mAdapter.getRemoteDevice(address.toUpperCase())); + } + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onLockStatusChanged(appId, setId, value, status, lockMembers); + } + } + + private void onLockAvailable (int appId, int setId, String address) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onLockAvailable(appId, setId, device); + } + } + + private void onSetSizeChanged (int setId, int size, String address) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onSetSizeChanged(setId, size, device); + } + } + + private void onSetSirkChanged(int setId, byte[] sirk, String address) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onSetSirkChanged(setId, sirk, device); + } + } + + // Native methods that call JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native void registerCsipAppNative(long appUuidLsb, long appUuidMsb); + private native void unregisterCsipAppNative(int appId); + private native void setLockValueNative(int appId, int setId, int value, String[] devicesList); + private native boolean connectSetDeviceNative(int appId, byte[] address); + private native boolean disconnectSetDeviceNative(int appId, byte[] address); +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..bc192b2f3cf2448bf439354b255c784942b30078 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java @@ -0,0 +1,529 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.groupclient; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDeviceGroup; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.SystemProperties; + +import android.util.Log; + +import com.android.bluetooth.btservice.AdapterService; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; + +/** + * Class that handles Bluetooth LE Scan results for Set member discovery. + * It performs scan result resolution for set identification. + * + * @hide + */ +public class GroupScanner { + private static final boolean DBG = true; + private static final boolean VDBG = GroupService.VDBG; + private static final String TAG = "BluetoothGroupScanner"; + + // messages for handling filtered PSRI Scan results + private static final int MSG_HANDLE_LE_SCAN_RESULT = 0; + + // message for starting coordinated set discovery + private static final int MSG_START_SET_DISCOVERY = 1; + + // message to stop coordinated set discovery + private static final int MSG_STOP_SET_DISCOVERY = 2; + + // message when set member discovery timeout happens + private static final int MSG_SET_MEMBER_DISC_TIMEOUT = 3; + + // message to handle PSRI from EIR packet + private static final int MSG_HANDLE_EIR_RESPONSE = 4; + + // PSRI Service AD Type + private final ParcelUuid PSRI_SERVICE_ADTYPE_UUID + = BluetoothUuid.parseUuidFrom(new byte[]{0x2E, 0x00}); + + private static final int PSRI_LEN = 6; + private static final int PSRI_SPLIT_LEN = 3; // 24 bits + private static final int AES_128_IO_LEN = 16; + + // Set Member Discovery timeout + private static final int SET_MEMBER_DISCOVERY_TIMEOUT = 10000; // 10 sec + + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mCurrentDevice; + private BluetoothLeScanner mScanner; + private GroupService mGroupService; + private volatile CsipHandler mHandler; + private Handler mainHandler; + private boolean mScanResolution; + private int mDiscoveryStoppedReason; + private CsipLeScanCallback mCsipScanCallback; + + // parameters for set discovery + private int mSetId; + private byte[] mSirk; + private int mTransport; + private int mSetSize; + private int mTotalDiscovered; + + private int mScanType = 1; + + // filter out duplicate scans + ArrayList scannedDevices = new ArrayList(); + + GroupScanner(GroupService service) { + mGroupService = service; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + // register receiver for Bluetooth State change + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + mGroupService.registerReceiver(mReceiver, filter); + + mainHandler = new Handler(mGroupService.getMainLooper()); + HandlerThread thread = new HandlerThread("CsipScanHandlerThread"); + thread.start(); + mHandler = new CsipHandler(thread.getLooper()); + mCsipScanCallback = new CsipLeScanCallback(); + ScanRecord.DATA_TYPE_GROUP_AD_TYPE = 0x2E; + /* Testing: Property used for deciding scan and filter type. To be removed */ + mScanType = SystemProperties.getInt( + "persist.vendor.service.bt.csip.scantype", 1); + } + + // Handler for CSIP scan operations and set member resolution. + private class CsipHandler extends Handler { + CsipHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (VDBG) Log.v(TAG, "msg.what = " + msg.what); + + switch (msg.what) { + case MSG_HANDLE_LE_SCAN_RESULT: + // start processing scan result + int callBackType = msg.arg1; + ScanResult result = (ScanResult) msg.obj; + mCurrentDevice = result.getDevice(); + + /* In case of DUMO device if advertisement is coming from other RPA */ + if (mCurrentDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + return; + } + + // skip scanresult if already processed for this device + if (scannedDevices.contains(mCurrentDevice)) { + if (VDBG) { + Log.w(TAG, "duplicate scanned result or Device" + + mCurrentDevice + " Group info already resolved. Ignore"); + } + return; + } + + scannedDevices.add(mCurrentDevice); + ScanRecord record = result.getScanRecord(); + + // get required service data with PSRI AD Type + byte[] srvcData = null; + /* for debugging purpose */ + if (mScanType == 2) { + srvcData = record.getServiceData(PSRI_SERVICE_ADTYPE_UUID); + } else { + srvcData = record.getGroupIdentifierData(); + } + if (srvcData == null || srvcData.length != PSRI_LEN) { + Log.e(TAG, "Group info with incorrect length found " + + "in advertisement of " + mCurrentDevice); + return; + } + + startPsriResolution(srvcData); + break; + + case MSG_HANDLE_EIR_RESPONSE: + EirData eirData = (EirData)msg.obj; + mCurrentDevice = eirData.curDevice; + byte[] eirGroupData = eirData.groupData; + + // skip eir if already processed for this device + if (scannedDevices.contains(mCurrentDevice)) { + if (VDBG) { + Log.w(TAG, "duplicate eir or Device" + + mCurrentDevice + " PSRI already resolved. Ignore"); + } + return; + } + + scannedDevices.add(mCurrentDevice); + if (eirGroupData == null || eirGroupData.length != PSRI_LEN) { + Log.e(TAG, "PSRI data with incorrect length found " + + "in EIR of " + mCurrentDevice); + return; + } + startPsriResolution(eirGroupData); + break; + + // High priority msg received in front of the message queue + case MSG_SET_MEMBER_DISC_TIMEOUT: + mDiscoveryStoppedReason = BluetoothDeviceGroup.DISCOVERY_STOPPED_BY_TIMEOUT; + case MSG_STOP_SET_DISCOVERY: + handleStopSetDiscovery(); + break; + + // High priority msg received in front of the message queue + case MSG_START_SET_DISCOVERY: + handleStartSetDiscovery(); + break; + + default: + Log.e(TAG, "Unknown message : " + msg.what); + } + } + } + + /* BroadcastReceiver for BT ON State intent for registering BLE Scanner */ + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.e(TAG, "Received intent with null action"); + return; + } + + switch (action) { + case BluetoothAdapter.ACTION_STATE_CHANGED: + mScanner = mBluetoothAdapter.getBluetoothLeScanner(); + break; + } + + } + }; + + /* Scan results callback */ + private class CsipLeScanCallback extends ScanCallback { + @Override + public void onScanResult(int callBackType, ScanResult result) { + if (VDBG) Log.v(TAG, "onScanResult callBackType : " + callBackType); + if (mHandler != null && mScanResolution) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_HANDLE_LE_SCAN_RESULT, + callBackType, 0, result)); + } else { + if (VDBG) Log.e(TAG, "onScanResult mHandler is null" + + " or Scan Resolution is stopped"); + } + } + + public void onScanFailed(int errorCode) { + mScanResolution = false; + Log.e(TAG, "Scan failed. Error code: " + new Integer(errorCode).toString()); + } + } + + /* EIR Data */ + private class EirData { + private BluetoothDevice curDevice; + private byte[] groupData; + + EirData(BluetoothDevice device, byte[] data) { + curDevice = device; + groupData = data; + } + } + + /* API that handles PSRI received from EIR */ + public void handleEIRGroupData(BluetoothDevice device, byte[] data) { + if (VDBG) Log.v(TAG, "handleEirData: device: " + device); + if (mHandler != null && mScanResolution) { + EirData eirData = new EirData(device, data); + mHandler.sendMessage(mHandler.obtainMessage(MSG_HANDLE_EIR_RESPONSE, eirData)); + } else { + if (VDBG) Log.e(TAG, "handleEirData mHandler is null" + + " or Inquiry Scan Resolution is stopped"); + } + } + + /* API to start set discovery by starting either LE scan or BREDR Inquiry */ + void startSetDiscovery(int setId, byte[] sirk, int transport, + int size, List setDevices) { + Log.d(TAG, "startGroupDiscovery: groupId: " + setId + ", group size = " + + size + ", Total discovered = " + setDevices.size() + + " Transport = " + transport); + + // check if set discovery is already in progress + if (mScanResolution) { + Log.e(TAG, "Group discovery is already in progress for Group Id: " + mSetId + + ". Ignore this request"); + return; + } + + // mark parameters of the set to be discovered + mSetId = setId; + mTransport = transport; + mSetSize = size; + mTotalDiscovered = setDevices.size(); + mSirk = Arrays.copyOf(sirk, AES_128_IO_LEN); + reverseByteArray(mSirk); + + // clear scanned arrayList and add already found set members to it + scannedDevices.clear(); + scannedDevices.addAll(setDevices); + + //post message in the front of message queue + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_START_SET_DISCOVERY)); + } + + /* API to start discovery with required settings and transport */ + void handleStartSetDiscovery() { + Log.d(TAG, "handleStartGroupDiscovery"); + mScanResolution = true; + + if (mTransport == BluetoothDevice.DEVICE_TYPE_CLASSIC) { + // start BREDR inquiry (unfiltered) + mBluetoothAdapter.startDiscovery(); + } else { + // Confiigure scan filter and start filtered scan for PSRI data + ScanSettings.Builder settingBuilder = new ScanSettings.Builder(); + List filters = new ArrayList(); + byte[] psri = {}; + + mScanType = SystemProperties.getInt( + "persist.vendor.service.bt.csip.scantype", 1); + + // for debugging purpose only + if (mScanType == 2) { + filters.add(new ScanFilter.Builder().setServiceData( + PSRI_SERVICE_ADTYPE_UUID, psri).build()); + } else if (mScanType == 1) { + filters.add(new ScanFilter.Builder().setGroupBasedFiltering(true) + .build()); + } + settingBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setLegacy(false); + + // start BLE filtered Scan + if (mScanType != 0) { + Log.i(TAG, " filtered scan started");// debug + mScanner.startScan(filters, settingBuilder.build(), mCsipScanCallback); + } else { + Log.i(TAG, " Unfiltered scan started");// debug + mScanner.startScan(mCsipScanCallback); + } + } + + // Start Set Member discovery timeout of 10 sec + mHandler.removeMessages(MSG_SET_MEMBER_DISC_TIMEOUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_SET_MEMBER_DISC_TIMEOUT), + SET_MEMBER_DISCOVERY_TIMEOUT); + } + + /* to stop set discorvey procedure - stop LE scan or BREDR inquiry */ + void stopSetDiscovery(int setId, int reason) { + Log.d(TAG, "stopGroupDiscovery"); + + mDiscoveryStoppedReason = reason; + + //post message in the front of message queue + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_STOP_SET_DISCOVERY)); + } + + /* handles actions to be taken once set discovery is needed to be stopped*/ + void handleStopSetDiscovery() { + Log.d(TAG, "handleStopGroupDiscovery"); + mScanResolution = false; + + if (mTransport == BluetoothDevice.DEVICE_TYPE_LE || + mTransport == BluetoothDevice.DEVICE_TYPE_DUAL) { + mScanner.stopScan(mCsipScanCallback); + } else { + mBluetoothAdapter.cancelDiscovery(); + } + + // remove all the queued scan results and set member discovery timeout message + mHandler.removeMessages(MSG_HANDLE_LE_SCAN_RESULT); + mHandler.removeMessages(MSG_SET_MEMBER_DISC_TIMEOUT); + + // Give callback to service to route it to requesting application + mainHandler.post(new Runnable() { + @Override + public void run() { + mGroupService.onSetDiscoveryCompleted( + mSetId, mTotalDiscovered, mDiscoveryStoppedReason); + } + }); + } + + /* Starts resolution of PSRI data received in scan results */ + void startPsriResolution(byte[] psri) { + Log.d(TAG, "startGroupResolution"); + + if (VDBG) printByteArrayInHex(psri, "GroupInfo"); + // obtain remote hash and random number + byte[] remoteHash = new byte[PSRI_SPLIT_LEN]; + byte[] randomNumber = new byte[PSRI_SPLIT_LEN]; + + // Get remote hash from first 24 bits of PSRI + System.arraycopy(psri, 0, remoteHash, 0, PSRI_SPLIT_LEN); + // Get random number from last 24 bits of PSRI + System.arraycopy(psri, PSRI_SPLIT_LEN, randomNumber, 0, PSRI_SPLIT_LEN); + + byte[] localHash = computeLocalHash(randomNumber); + + if (VDBG) { + printByteArrayInHex(localHash, "localHash"); + printByteArrayInHex(remoteHash, "remoteHash"); + } + + if (localHash != null) { + validateSetMember(localHash, remoteHash); + } + } + + /* computes local hash from received random number and SIRK */ + byte[] computeLocalHash(byte[] randomNumber) { + byte[] localHash = new byte[AES_128_IO_LEN]; + byte[] randomNumber128 = new byte[AES_128_IO_LEN]; + System.arraycopy(randomNumber, 0, randomNumber128, 0, PSRI_SPLIT_LEN); + + reverseByteArray(randomNumber128); + + if (VDBG) { + // for debugging + printByteArrayInHex(mSirk, "reversed GroupIRK"); + printByteArrayInHex(randomNumber128, "reverse randomNumber"); + } + + try { + SecretKeySpec skeySpec = new SecretKeySpec(mSirk, "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec); + localHash = cipher.doFinal(randomNumber128); + reverseByteArray(localHash); + if (VDBG) printByteArrayInHex(localHash, "after AES 128 encryption"); + return Arrays.copyOfRange(localHash, 0, PSRI_SPLIT_LEN); + } catch (Exception e) { + Log.e(TAG, "Exception while generating local hash: " + e); + } + return null; + } + + /* to validate that if remote belongs to a given coordinated set*/ + void validateSetMember(byte[] localHash, byte[] remoteHash) { + if (!Arrays.equals(localHash, remoteHash)) { + return; + } + Log.d(TAG, "New Group device discovered: " + mCurrentDevice); + mTotalDiscovered++; + + // give set member found callback on main thread + mainHandler.post(new Runnable() { + @Override + public void run() { + mGroupService.onSetMemberFound(mSetId, mCurrentDevice); + } + }); + + //check if all set members have been discovered + if (mSetSize > 0 && mTotalDiscovered >= mSetSize) { + // to immediatly ignore processing scan results after completion + mScanResolution = false; + mDiscoveryStoppedReason = BluetoothDeviceGroup.DISCOVERY_COMPLETED; + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_STOP_SET_DISCOVERY)); + } else { + // restart set member discovery timeout + mHandler.removeMessages(MSG_SET_MEMBER_DISC_TIMEOUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_SET_MEMBER_DISC_TIMEOUT), + SET_MEMBER_DISCOVERY_TIMEOUT); + } + + } + + /* cleanup tasks on BT OFF*/ + void cleanup() { + mGroupService.unregisterReceiver(mReceiver); + } + + // returns reversed byte array + void reverseByteArray(byte[] byte_arr) { + int size = byte_arr.length; + for (int i = 0; i < size/2; i++) { + byte b = byte_arr[i]; + byte_arr[i] = byte_arr[size - 1 - i]; + byte_arr[size - 1 - i] = b; + } + } + + public static byte[] hexStringToByteArray(String str) { + byte[] b = new byte[str.length() / 2]; + for (int i = 0; i < b.length; i++) { + int index = i * 2; + int val = Integer.parseInt(str.substring(index, index + 2), 16); + b[i] = (byte) val; + } + return b; + } + + // print byte array in hexadecimal format + void printByteArrayInHex(byte[] data, String name) { + final StringBuilder hex = new StringBuilder(); + for(byte b : data) { + hex.append(String.format("%02x", b)); + } + Log.i(TAG, name + ": " + hex); + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java new file mode 100644 index 0000000000000000000000000000000000000000..5f8ba52e512087862b2d58539f5f0416412b3990 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java @@ -0,0 +1,1107 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.groupclient; + +import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; +import android.bluetooth.BluetoothDeviceGroup; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.DeviceGroup; +import android.bluetooth.IBluetoothDeviceGroup; +import android.bluetooth.IBluetoothGroupCallback; +import android.bluetooth.BluetoothGroupCallback; +import android.content.AttributionSource; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ParcelUuid; +import android.os.SystemProperties; + +import android.util.Log; + +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.Config; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.bluetooth.Utils; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * Provides Bluetooth CSIP Client profile, as a service in the Bluetooth application. + * @hide + */ +public class GroupService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = "BluetoothGroupService"; + protected static final boolean VDBG = true;//Log.isLoggable(TAG, Log.VERBOSE); + + private GroupScanner mGroupScanner; + + private static GroupService sGroupService; + + private AdapterService mAdapterService; + + GroupClientNativeInterface mGroupNativeInterface; + + GroupAppMap mAppMap = new GroupAppMap(); + + private static CopyOnWriteArrayList mCoordinatedSets + = new CopyOnWriteArrayList(); + + private static HashMap setSirkMap = new HashMap(); + + private static final int INVALID_APP_ID = 0x10; + private static final int INVALID_SET_ID = 0x10; + private static final UUID EMPTY_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + /* parameters to hold details for ongoing set discovery and pending set discovery */ + private SetDiscoveryRequest mCurrentSetDisc = null; + private SetDiscoveryRequest mPendingSetDisc = null; + + /* Constants for Coordinated set properties */ + private static final String SET_ID = "SET_ID"; + private static final String INCLUDING_SRVC = "INCLUDING_SRVC"; + private static final String SIZE = "SIZE"; + private static final String SIRK = "SIRK"; + private static final String LOCK_SUPPORT = "LOCK_SUPPORT"; + + private class SetDiscoveryRequest { + private int mAppId = INVALID_APP_ID; + private int mSetId = INVALID_SET_ID; + private boolean mDiscInProgress = false; + + SetDiscoveryRequest() { + mAppId = INVALID_APP_ID; + mSetId = INVALID_SET_ID; + mDiscInProgress = false; + } + + SetDiscoveryRequest(int appId, int setId, boolean inProgress) { + mAppId = appId; + mSetId = setId; + mDiscInProgress = inProgress; + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.e(TAG, "Received intent with null action"); + return; + } + + switch (action) { + case BluetoothDevice.ACTION_BOND_STATE_CHANGED: + BluetoothDevice device = intent.getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE); + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + if (bondState == BluetoothDevice.BOND_NONE) { + int setId = getRemoteDeviceGroupId(device, null); + if (setId < BluetoothDeviceGroup.INVALID_GROUP_ID) { + Log.i(TAG, " Group Device "+ device + + " unpaired. Group ID: " + setId); + removeSetMemberFromCSet(setId, device); + } + } + break; + } + + } + }; + + + @Override + protected IProfileServiceBinder initBinder() { + return new GroupBinder(this); + } + + @Override + protected boolean start() { + if (DBG) { + Log.d(TAG, "start()"); + } + + mGroupNativeInterface = Objects.requireNonNull(GroupClientNativeInterface.getInstance(), + "GroupClientNativeInterface cannot be null when GroupService starts"); + + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when GroupService starts"); + + mGroupScanner = new GroupScanner(this); + + mGroupNativeInterface.init(); + setGroupService(this); + + // register receiver for Bluetooth State change + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + registerReceiver(mReceiver, filter); + + return true; + } + + private static synchronized void setGroupService(GroupService instance) { + if (DBG) { + Log.d(TAG, "setGroupService(): set to: " + instance); + } + sGroupService = instance; + } + + protected boolean stop() { + if (DBG) { + Log.d(TAG, "stop()"); + } + + if (mGroupScanner != null) { + mGroupScanner.cleanup(); + } + + if (sGroupService == null) { + Log.w(TAG, "stop() called already.."); + return true; + } + + unregisterReceiver(mReceiver); + + // Cleanup native interface + mGroupNativeInterface.cleanup(); + mGroupNativeInterface = null; + + // Mark service as stopped + setGroupService(null); + + // cleanup initializations + mGroupScanner = null; + mAdapterService = null; + + return true; + } + + @Override + protected void cleanup() { + if (DBG) { + Log.d(TAG, "cleanup()"); + } + + // Cleanup native interface + if (mGroupNativeInterface != null) { + mGroupNativeInterface.cleanup(); + mGroupNativeInterface = null; + } + + // cleanup initializations + mGroupScanner = null; + mAdapterService = null; + } + + /** + * Get the GroupService instance + * @return GroupService instance + */ + public static synchronized GroupService getGroupService() { + if (sGroupService == null) { + Log.w(TAG, "getGroupService(): service is NULL"); + return null; + } + + if (!sGroupService.isAvailable()) { + Log.w(TAG, "getGroupService(): service is not available"); + return null; + } + + return sGroupService; + } + + /* API to load coordinated set from bonded device on BT ON */ + public static void loadDeviceGroupFromBondedDevice ( + BluetoothDevice device, String setDetails) { + String[] csets = setDetails.split(" "); + if (VDBG) Log.v(TAG, " Device is part of " + csets.length + " device groups"); + + for (String setInfo: csets) { + String[] setProperties = setInfo.split("~"); + int setId = INVALID_SET_ID, size = 0; + UUID inclSrvcUuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); + boolean lockSupport = false; + + for (String property: setProperties) { + if (VDBG) Log.v(TAG, "Property = " + property); + String[] propSplit = property.split(":"); + if (propSplit[0].equals(SET_ID)) { + setId = Integer.parseInt(propSplit[1]); + } else if (propSplit[0].equals(INCLUDING_SRVC)) { + inclSrvcUuid = UUID.fromString(propSplit[1]); + } else if (propSplit[0].equals(SIZE)) { + size = Integer.parseInt(propSplit[1]); + } else if (propSplit[0].equals(SIRK) && setId != 16) { + setSirkMap.put(setId, GroupScanner.hexStringToByteArray(propSplit[1])); + } else if (propSplit[0].equals(LOCK_SUPPORT)) { + lockSupport = Boolean.parseBoolean(propSplit[1]); + } + } + + DeviceGroup set = getCoordinatedSet(setId, false); + if (set == null) { + List members = new ArrayList(); + members.add(device); + set = new DeviceGroup(setId, size, members, + new ParcelUuid(inclSrvcUuid), lockSupport); + mCoordinatedSets.add(set); + } else { + if (!set.getDeviceGroupMembers().contains(device)) { + set.getDeviceGroupMembers().add(device); + } + } + if (VDBG) Log.v(TAG, "Device " + device + " loaded in Group ("+ setId +")" + + " Devices: " + set.getDeviceGroupMembers()); + } + } + + /* API to accept PSRI data from EIR packet */ + public void handleEIRGroupData(BluetoothDevice device, String data) { + mGroupScanner.handleEIRGroupData(device, data.getBytes()); + } + + public static void setAdvanceAudioSupport() { + Log.d(TAG, "setAdvanceAudioSupport: Setting support from LEA Module"); + + if (SystemProperties.get("persist.vendor.service.bt.adv_audio_mask").isEmpty()) { + SystemProperties.set("persist.vendor.service.bt.adv_audio_mask", + String.valueOf(Config.ADV_AUDIO_UNICAST_FEAT_MASK | + Config.ADV_AUDIO_BCA_FEAT_MASK | + Config.ADV_AUDIO_BCS_FEAT_MASK)); + } + } + + private static class GroupBinder + extends IBluetoothDeviceGroup.Stub implements IProfileServiceBinder { + private GroupService mService; + + private GroupService getService() { + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + GroupBinder(GroupService service) { + if (DBG) { + Log.v(TAG, "GroupBinder()"); + } + mService = service; + } + + @Override + public void cleanup() { + mService = null; + } + + @Override + public void connect(int appId, BluetoothDevice device, AttributionSource source) { + if (DBG) { + Log.d(TAG, "connect Device " + device); + } + + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "connect")) { + return; + } + service.connect(appId, device); + } + + @Override + public void disconnect(int appId, BluetoothDevice device, AttributionSource source) { + if (DBG) { + Log.d(TAG, "disconnect Device " + device); + } + + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "disconnect")) { + return; + } + service.disconnect(appId, device); + } + + @Override + public void registerGroupClientApp(ParcelUuid uuid, + IBluetoothGroupCallback callback, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "registerGroupClientApp"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "registerGroupClientApp")) { + return; + } + service.registerGroupClientApp(uuid.getUuid(), callback, null); + } + + @Override + public void unregisterGroupClientApp(int appId, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "unregisterGroupClientApp"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "unregisterGroupClientApp")) { + return; + } + service.unregisterGroupClientApp(appId); + } + + @Override + public void setExclusiveAccess(int appId, int groupId, List devices, + int value, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "setExclusiveAccess"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "setExclusiveAccess")) { + return; + } + service.setLockValue(appId, groupId, devices, value); + } + + @Override + public void startGroupDiscovery(int appId, int groupId + , AttributionSource source) throws RemoteException { + if (VDBG) { + Log.d(TAG, "startGroupDiscovery"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery(service, + source, "startGroupDiscovery")) { + return; + } + service.startSetDiscovery(appId, groupId); + } + + @Override + public void stopGroupDiscovery(int appId, int groupId, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "stopGroupDiscovery"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "stopGroupDiscovery")) { + return; + } + enforceBluetoothPrivilegedPermission(service); + service.stopSetDiscovery(appId, groupId); + } + + @Override + public void getExclusiveAccessStatus(int appId, int groupId, + List devices, AttributionSource source) { + if (DBG) { + Log.d(TAG, "getExclusiveAccessStatus"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getExclusiveAccessStatus")) { + return; + } + enforceBluetoothPrivilegedPermission(service); + } + + @Override + public List getDiscoveredGroups(boolean mPublicAddr + , AttributionSource source) { + if (DBG) { + Log.d(TAG, "getDiscoveredGroups"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getDiscoveredGroups")) { + return null; + } + return service.getDiscoveredCoordinatedSets(mPublicAddr); + } + + @Override + public DeviceGroup getDeviceGroup(int setId, boolean mPublicAddr, + AttributionSource source) { + if (DBG) { + Log.d(TAG, "getDeviceGroup"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getDeviceGroup")) { + return null; + } + return (service.getCoordinatedSet(setId, mPublicAddr)); + } + + @Override + public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid, + boolean mPublicAddr, AttributionSource source) { + if (DBG) { + Log.d(TAG, "getRemoteDeviceGroupId"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getRemoteDeviceGroupId")) { + return INVALID_SET_ID; + } + return service.getRemoteDeviceGroupId(device, uuid, mPublicAddr); + } + + @Override + public boolean isGroupDiscoveryInProgress (int setId, AttributionSource source) { + if (DBG) { + Log.d(TAG, "isGroupDiscoveryInProgress"); + } + GroupService service = getService(); + if (service == null || !Utils.checkScanPermissionForDataDelivery( + service, source, "isGroupDiscoveryInProgress")) { + return false; + } + return service.isSetDiscoveryInProgress(setId); + } + }; + + /** + * DeathReceipient handler to unregister applications those are + * disconnected ungracefully (ie. crash or forced close). + */ + class GroupAppDeathRecipient implements IBinder.DeathRecipient { + int mAppId; + + GroupAppDeathRecipient(int appId) { + mAppId = appId; + Log.i(TAG, "GroupAppDeathRecipient"); + } + + @Override + public void binderDied() { + if (DBG) { + Log.d(TAG, "Binder is dead - unregistering app (" + mAppId + ")!"); + } + + mAppMap.remove(mAppId); + unregisterGroupClientApp(mAppId); + } + + } + + /* for registration of other Bluetooth profile in Bluetooth App Space*/ + public void registerGroupClientModule(BluetoothGroupCallback callback) { + Log.d(TAG, "registerGroupClientModule"); + + UUID uuid; + + if (mGroupNativeInterface == null) return; + // Generate an unique UUID for Bluetooth Modules which is not used by others apps + do { + uuid = UUID.randomUUID(); + } while(mAppMap.appUuids.contains(uuid)); + + registerGroupClientApp(uuid, null, callback); + } + + /* Registers CSIP App or module with CSIP native layer */ + public void registerGroupClientApp(UUID uuid, IBluetoothGroupCallback appCb, + BluetoothGroupCallback localCallback) { + if (DBG) { + Log.d(TAG, "registerGroupClientApp: UUID = " + uuid.toString()); + } + + boolean isLocal = false; + if (localCallback != null) { + isLocal = true; + } + + mAppMap.add(uuid, isLocal, appCb, localCallback); + mGroupNativeInterface.registerCsipApp(uuid.getLeastSignificantBits(), + uuid.getMostSignificantBits()); + } + + /* Unregisters Bluetooth module (BT profile) with CSIP*/ + public void unregisterGroupClientModule(int appId) { + unregisterGroupClientApp(appId); + } + + /* Unregisters App/Module with CSIP*/ + public void unregisterGroupClientApp(int appId) { + if (DBG) { + Log.d(TAG, "unregisterGroupClientApp: appId = " + appId); + } + + if (mGroupNativeInterface == null) return; + mAppMap.remove(appId); + mGroupNativeInterface.unregisterCsipApp(appId); + } + + /* API to request change in lock value */ + public void setLockValue(int appId, int setId, List devices, + int value) { + if (DBG) { + Log.d(TAG, "setExclusiveAccess: appId = " + appId + ", setId: " + setId + + ", value = " + value + ", set Members = " + devices); + } + + if (mGroupNativeInterface == null) return; + // appId and setId validation is done at stack layer + mGroupNativeInterface.setLockValue(appId, setId, devices, value); + } + + /* Starts the set members discovery for the requested coordinated set */ + public void startSetDiscovery(int appId, int setId) throws RemoteException { + if (DBG) { + Log.d(TAG, "startGroupDiscovery. setId = " + setId + " Initiating appId = " + appId); + } + + // Get Apllication details + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + DeviceGroup cSet = getCoordinatedSet(setId, true); + if (cSet == null || !setSirkMap.containsKey(setId)) { + Log.e(TAG, "Invalid Group Id: " + setId); + mCurrentSetDisc = null; + app.appCb.onGroupDiscoveryStatusChanged(setId, BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, + BluetoothDeviceGroup.DISCOVERY_NOT_STARTED_INVALID_PARAMS); + return; + } + + /* check if all set members are already discovered */ + int setSize = cSet.getDeviceGroupSize(); + if (setSize != 0 && cSet.getTotalDiscoveredGroupDevices() >= setSize) { + app.appCb.onGroupDiscoveryStatusChanged(setId, + BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, + BluetoothDeviceGroup.DISCOVERY_COMPLETED); + return; + } + + if (mCurrentSetDisc != null && mCurrentSetDisc.mDiscInProgress) { + Log.e(TAG, "Group Discovery is already in Progress for Group: " + + mCurrentSetDisc.mSetId + " from AppId: " + mCurrentSetDisc.mAppId + + " Stop current Group discovery"); + mPendingSetDisc = new SetDiscoveryRequest(appId, setId, false); + mGroupScanner.stopSetDiscovery(mCurrentSetDisc.mSetId, 0); + return; + } else if (mCurrentSetDisc == null) { + mCurrentSetDisc = new SetDiscoveryRequest(appId, setId, false); + } + + int transport; + byte[] sirk; + + sirk = setSirkMap.get(setId); + + /*TODO: Optimize logic if device type is UNKNOWN */ + try { + BluetoothDevice device = cSet.getDeviceGroupMembers().get(0); + transport = device.getType(); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "Invalid Group- No device found : " + e); + mCurrentSetDisc = null; + return; + } + + mGroupScanner.startSetDiscovery(setId, sirk, transport, + cSet.getDeviceGroupSize(), cSet.getDeviceGroupMembers()); + mCurrentSetDisc.mDiscInProgress = true; + + try { + if (app.appCb != null) { + app.appCb.onGroupDiscoveryStatusChanged(setId, + BluetoothDeviceGroup.GROUP_DISCOVERY_STARTED, + BluetoothDeviceGroup.DISCOVERY_STARTED_BY_APPL); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Stops the set members discovery for the requested coordinated set */ + public void stopSetDiscovery(int appId, int setId) { + if (DBG) { + Log.d(TAG, "stopGroupDiscovery: appId = " + appId + " groupId = " + setId); + } + + // Get Apllication details + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + // check if requesting app is stopping the discovery + if (mCurrentSetDisc == null || mCurrentSetDisc.mAppId != appId) { + Log.e(TAG, " Either no discovery in progress or Stop Request from" + + " App which has not started Group Discovery"); + return; + } + + mGroupScanner.stopSetDiscovery(setId, BluetoothDeviceGroup.DISCOVERY_STOPPED_BY_APPL); + } + + /* Reading lock status of coordinated set for ordered access procedure */ + public void getLockStatus(int setId, List devices) { + //TODO: Future enhancement + } + + /* To connect to Coordinated Set Device */ + public void connect (int appId, BluetoothDevice device) { + Log.d(TAG, "connect Device: " + device + ", appId: " + appId); + if (mGroupNativeInterface == null) return; + mGroupNativeInterface.connectSetDevice(appId, device); + } + + /* To disconnect from Coordinated Set Device */ + public void disconnect (int appId, BluetoothDevice device) { + Log.d(TAG, "disconnect Device: " + device + ", appId: " + appId); + if (mGroupNativeInterface == null) return; + mGroupNativeInterface.disconnectSetDevice(appId, device); + } + + public List getDiscoveredCoordinatedSets() { + return getDiscoveredCoordinatedSets(true); + } + + /* returns all discovered coordinated sets */ + public List getDiscoveredCoordinatedSets(boolean mPublicAddr) { + if (DBG) { + Log.d(TAG, "getDiscoveredGroups"); + } + + /* Add logic to replace random addresses to public addresses if requested */ + // Iterate on coordinated sets + // check address type. Replace with public if requested + if (mPublicAddr) { + List coordinatedSets = new ArrayList(); + AdapterService adapterService = Objects.requireNonNull( + AdapterService.getAdapterService(), + "AdapterService cannot be null"); + for (DeviceGroup set: mCoordinatedSets) { + DeviceGroup cSet = new DeviceGroup( + set.getDeviceGroupId(), set.getDeviceGroupSize(), + new ArrayList(), + set.getIncludingServiceUUID(), set.isExclusiveAccessSupported()); + for (BluetoothDevice device: set.getDeviceGroupMembers()) { + BluetoothDevice publicDevice = device; + if (adapterService.isIgnoreDevice(device)) { + publicDevice = adapterService.getIdentityAddress(device); + } + cSet.getDeviceGroupMembers().add(publicDevice); + } + coordinatedSets.add(cSet); + } + return coordinatedSets; + } + + return mCoordinatedSets; + } + + public static DeviceGroup getCoordinatedSet(int setId) { + return getCoordinatedSet(setId, true); + } + + /* returns requested coordinated set */ + public static DeviceGroup getCoordinatedSet(int setId, boolean mPublicAddr) { + if (DBG) { + Log.d(TAG, "getDeviceGroup : groupId = " + setId + + " mPublicAddr: " + mPublicAddr); + } + + AdapterService adapterService = Objects.requireNonNull( + AdapterService.getAdapterService(), "AdapterService cannot be null"); + + for (DeviceGroup cSet: mCoordinatedSets) { + if (cSet.getDeviceGroupId() == setId) { + if (!mPublicAddr) { + return cSet; + + // Public addresses are requested. Replace address with public addr + } else { + DeviceGroup set = new DeviceGroup( + cSet.getDeviceGroupId(), cSet.getDeviceGroupSize(), + new ArrayList(), + cSet.getIncludingServiceUUID(), cSet.isExclusiveAccessSupported()); + for (BluetoothDevice device: cSet.getDeviceGroupMembers()) { + if (device.getBondState() == BluetoothDevice.BOND_BONDED) { + BluetoothDevice publicDevice = device; + if (adapterService.isIgnoreDevice(device)) { + publicDevice = adapterService.getIdentityAddress(device); + } + set.getDeviceGroupMembers().add(publicDevice); + } + } + return set; + } + } + } + + return null; + } + + public boolean isSetDiscoveryInProgress (int setId) { + if (DBG) { + Log.d(TAG, "isGroupDiscoveryInProgress: groupId = " + setId); + } + + if (mCurrentSetDisc != null && mCurrentSetDisc.mSetId == setId + && mCurrentSetDisc.mDiscInProgress) + return true; + return false; + } + + public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid) { + return getRemoteDeviceGroupId(device, uuid, true); + } + + public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid, + boolean mPublicAddr) { + if (DBG) { + Log.d(TAG, "getRemoteDeviceGroupId: device = " + device + " uuid = " + uuid + + ", mPublicAddr = " + mPublicAddr); + } + + if (mAdapterService == null) { + Log.e(TAG, "AdapterService instance is NULL. Return."); + return INVALID_SET_ID; + } + + BluetoothDevice setDevice = null; + if (mPublicAddr && mAdapterService.isIgnoreDevice(device)) { + setDevice = mAdapterService.getIdentityAddress(device); + } + + if (uuid == null) { + uuid = new ParcelUuid(EMPTY_UUID); + } + + for (DeviceGroup cSet: mCoordinatedSets) { + if ((cSet.getDeviceGroupMembers().contains(device) || + cSet.getDeviceGroupMembers().contains(setDevice)) + && cSet.getIncludingServiceUUID().equals(uuid)) { + return cSet.getDeviceGroupId(); + } + } + + return INVALID_SET_ID; + } + + /* This API is called when pairing with LE Audio capable set member fails or + * when set member is unpaired. Removing the set member from list gives option + * to user to rediscover it */ + public void removeSetMemberFromCSet(int setId, BluetoothDevice device) { + Log.d(TAG, "removeDeviceFromDeviceGroup: setId = " + setId + ", Device: " + device); + + DeviceGroup cSet = getCoordinatedSet(setId, false); + if (cSet != null) { + cSet.getDeviceGroupMembers().remove(device); + if (cSet.getDeviceGroupMembers().size() == 0) { + Log.i(TAG, "Last device unpaired. Removing Device Group from database"); + mCoordinatedSets.remove(cSet); + return; + } + } + + cSet = getCoordinatedSet(setId, true); + if (cSet != null) { + cSet.getDeviceGroupMembers().remove(device); + if (cSet.getDeviceGroupMembers().size() == 0) { + Log.i(TAG, "Last device unpaired. Removing Device Group from database"); + mCoordinatedSets.remove(cSet); + } + } + } + + public void printAllCoordinatedSets() { + if (VDBG) { + for (DeviceGroup set: mCoordinatedSets) { + Log.i(TAG, "GROUP_ID: " + set.getDeviceGroupId() + + ", size = " + set.getDeviceGroupSize() + + ", discovered = " + set.getTotalDiscoveredGroupDevices() + + ", Including Srvc Uuid = "+ set.getIncludingServiceUUID() + + ", devices = " + set.getDeviceGroupMembers()); + } + } + } + + /* Callback received from CSIP native layer when an APP/module has been registered */ + protected void onCsipAppRegistered (int status, int appId, UUID uuid) { + Log.d(TAG, "onGroupClientAppRegistered: appId: " + appId + ", UUID: " + uuid.toString()); + + GroupAppMap.GroupClientApp app = mAppMap.getByUuid(uuid); + + if (app == null) { + Log.e(TAG, "Application not found for UUID: " + uuid.toString()); + return; + } + + app.appId = appId; + // Give callback to the application that app has been registered + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onGroupClientAppRegistered(status, appId); + } else if (app.isRegistered && !app.isLocal) { + app.linkToDeath(new GroupAppDeathRecipient(appId)); + app.appCb.onGroupClientAppRegistered(status, appId); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* When CSIP Profile connection state has been changed */ + protected void onConnectionStateChanged(int appId, BluetoothDevice device, + int state, int status) { + Log.d(TAG, "onConnectionStateChanged: appId: " + appId + ", device: " + device + + ", State: " + state + ", Status: " + status); + + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onConnectionStateChanged(state, device); + } else if (app.isRegistered && !app.isLocal) { + app.appCb.onConnectionStateChanged(state, device); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* When a new set member is discovered as a part of Set Discovery procedure */ + protected void onSetMemberFound (int setId, BluetoothDevice device) { + Log.d(TAG, "onGroupDeviceFound: groupId: " + setId + ", device: " + device); + for (DeviceGroup cSet: mCoordinatedSets) { + if (cSet.getDeviceGroupId() == setId + && !cSet.getDeviceGroupMembers().contains(device)) { + cSet.getDeviceGroupMembers().add(device); + break; + } + } + + // Give callback to adapterservice to initiate bonding if required + mAdapterService.processGroupMember(setId, device); + + // Give callback to the application that started Set Discovery + GroupAppMap.GroupClientApp app = mAppMap.getById(mCurrentSetDisc.mAppId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + mCurrentSetDisc.mAppId); + return; + } + + try { + if (app.appCb != null) { + app.appCb.onGroupDeviceFound(setId, device); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* When set discovery procedure has been completed */ + protected void onSetDiscoveryCompleted (int setId, + int totalDiscovered, int reason) { + Log.d(TAG, "onGroupDiscoveryCompleted: groupId: " + setId + ", totalDiscovered = " + + totalDiscovered + "reason: " + reason); + + // mark Set Discovery procedure as completed + mCurrentSetDisc.mDiscInProgress = false; + // Give callback to the application that Set Discovery has been completed + GroupAppMap.GroupClientApp app = mAppMap.getById(mCurrentSetDisc.mAppId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + mCurrentSetDisc.mAppId); + return; + } + + try { + if (app.appCb != null) { + app.appCb.onGroupDiscoveryStatusChanged(setId, + BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, reason); + } + + DeviceGroup cSet = getCoordinatedSet(setId, false); + if (VDBG && cSet != null) { + Log.i(TAG, "Device Group: groupId" + setId + ", devices: " + + cSet.getDeviceGroupMembers()); + } + + if (mPendingSetDisc != null) { + mCurrentSetDisc = mPendingSetDisc; + mPendingSetDisc = null; + startSetDiscovery(mCurrentSetDisc.mAppId, mCurrentSetDisc.mSetId); + } else { + mCurrentSetDisc = null; + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Callback received from CSIP native layer when a new Coordinated set has been + * identified with remote device */ + protected void onNewSetFound(int setId, BluetoothDevice device, int size, + byte[] sirk, UUID pSrvcUuid, boolean lockSupport) { + Log.d(TAG, "onNewGroupFound: Address : " + device + ", groupId: " + setId + + ", size: " + size + ", uuid: " + pSrvcUuid.toString()); + + // Form Coordinated Set Object and store in ArrayList + List devices = new ArrayList(); + devices.add(device); + DeviceGroup cSet = new DeviceGroup(setId, size, devices, + new ParcelUuid(pSrvcUuid), lockSupport); + mCoordinatedSets.add(cSet); + + // Store sirk in hashmap of setId, sirk + setSirkMap.put(setId, sirk); + + // Give Callback to all registered application + try { + for (GroupAppMap.GroupClientApp app: mAppMap.mApps) { + if (app.isRegistered && !app.isLocal) { + if (app.appCb != null)//temp check + app.appCb.onNewGroupFound(setId, device, new ParcelUuid(pSrvcUuid)); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Callback received from CSIP native layer when undiscovered set member is connected */ + protected void onNewSetMemberFound (int setId, BluetoothDevice device) { + Log.d(TAG, "onNewGroupDeviceFound: groupId = " + setId + ", Device = " + device); + + if (mAdapterService == null) { + Log.e(TAG, "AdapterService instance is NULL. Return."); + return; + } + + if (mAdapterService.isIgnoreDevice(device)) { + device = mAdapterService.getIdentityAddress(device); + } + // check if this device is already part of an already existing coordinated set + /* Scenario: When a device was not discovered during initial set discovery + * procedure and later user had explicitely paired with this device + * from pair new device UI option. Not required to send onSetMemberFound + * callback to application*/ + if (setSirkMap.containsKey(setId)) { + for (DeviceGroup cSet: mCoordinatedSets) { + if (cSet.getDeviceGroupId() == setId && + (!cSet.getDeviceGroupMembers().contains(device))) { + cSet.getDeviceGroupMembers().add(device); + break; + } + } + return; + } + } + + /* callback received when lock status is changed for requested coordinated set*/ + protected void onLockStatusChanged (int appId, int setId, int value, int status, + List devices) { + Log.d(TAG, "onExclusiveAccessChanged: appId = " + appId + ", groupId = " + setId + + ", value = " + value + ", status = " + status + ", devices = " + devices); + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onExclusiveAccessChanged(setId, value, status, devices); + } else if (app.isRegistered && !app.isLocal) { + app.appCb.onExclusiveAccessChanged(setId, value, status, devices); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Callback received when earlier denied lock is now available */ + protected void onLockAvailable (int appId, int setId, BluetoothDevice device) { + Log.d(TAG, "onExclusiveAccessAvailable: Remote(" + device + "), Group Id: " + + setId + ", App Id: " + appId); + + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onExclusiveAccessAvailable(setId, device); + } else if (app.isRegistered && !app.isLocal) { + app.appCb.onExclusiveAccessAvailable(setId, device); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + + } + + /* Callback received when set size has been changed */ + /* TODO: Scanarios are unknown. Actions are to be decided */ + protected void onSetSizeChanged (int setId, int size, BluetoothDevice device) { + Log.d(TAG, "onGroupSizeChanged: Group Id: " + setId + ", New Size: " + size + + ", Notifying device: " + device); + + // TODO: Logic to be incorporated once use case is understood + } + + /* Callback received when set SIRK has been changed */ + /* TODO: Scanarios are unknown. Actions are to be decided */ + protected void onSetSirkChanged(int setId, byte[] sirk, BluetoothDevice device) { + Log.d(TAG, "onGroupIdChanged Group Id: " + setId + ", Notifying device: " + device); + + // TODO: Logic to be incorporated once use case is understood + } + +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..b850ce9e3955269fbc2e546cb3a6dfa4c6631743 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java @@ -0,0 +1,274 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.mcp; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Mcp Native Interface to/from JNI. + */ +public class McpNativeInterface { + private static final String TAG = "McpNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + @GuardedBy("INSTANCE_LOCK") + private static McpNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private McpNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtfStack(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static McpNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new McpNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + + /** + * update MCP media supported feature + * @param feature + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaControlPointOpcodeSupported(int feature) { + return mediaControlPointOpcodeSupportedNative(feature); + } + + /** + * update MCP media supported feature current value + * @param value + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaControlPoint(int value) { + return mediaControlPointNative(value); + } + + /** + * Sets the Mcp media state + * @param state + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaState(int state) { + return mediaStateNative(state); + } + + /** + * update MCP media player name + * @param player name + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaPlayerName(String playeName) { + return mediaPlayerNameNative(playeName); + } + /** + * update track change notification + * @param track id + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackChanged(int status) { + return trackChangedNative(status); + } + /** + * update MCP track position + * @param position + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackPosition(int position) { + return trackPositionNative(position); + } + + /** + * update MCP track duration + * @param duration + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackDuration(int duration) { + return trackDurationNative(duration); + } + /** + * update MCP track title + * @param title + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackTitle(String title) { + return trackTitleNative(title); + } + /** + * update playing order support of media + * @param order + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean playingOrderSupported(int order) { + return playingOrderSupportedNative(order); + } + /** + * update playing order value of media + * @param value + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean playingOrder(int value) { + return playingOrderNative(value); + } + /** + * update active device + * @param device + * @param setId + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean setActiveDevice(BluetoothDevice device, int setId, int profile) { + return setActiveDeviceNative(profile, setId, getByteAddress(device)); + } + /** + * Sets Mcp media content control id + * @param ccid + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean contentControlId(int ccid) { + + return contentControlIdNative(ccid); + } + /** + * Disconnect Mcp disconnect device + * @param device + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectMcp(BluetoothDevice device) { + return disconnectMcpNative(getByteAddress(device)); + } + + /** + * Disconnect Mcp disconnect device + * @param device + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean bondStateChange(BluetoothDevice device, int state) { + return bondStateChangeNative(state, getByteAddress(device)); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + // Callbacks from the native stack back into the Java framework. + // All callbacks are routed via the Service which will disambiguate which + private void OnConnectionStateChanged(int state, byte[] address) { + if (DBG) { + Log.d(TAG, "OnConnectionStateChanged: " + state); + } + BluetoothDevice device = getDevice(address); + + McpService service = McpService.getMcpService(); + if (service != null) + service.onConnectionStateChanged(device, state); + } + + private void MediaControlPointChangedRequest(int state, byte[] address) { + BluetoothDevice device = getDevice(address); + if (DBG) { + Log.d(TAG, "MediaControlPointChangedReq: " + state); + } + McpService service = McpService.getMcpService(); + if (service != null) + service.onMediaControlPointChangeReq(device, state); + } + + private void TrackPositionChangedRequest(int position) { + if (DBG) { + Log.d(TAG, "TrackPositionChangedRequest: " + position); + } + McpService service = McpService.getMcpService(); + if (service != null) + service.onTrackPositionChangeReq(position); + } + + private void PlayingOrderChangedRequest(int order) { + if (DBG) { + Log.d(TAG, "PlayingOrderChangedRequest: " + order); + } + McpService service = McpService.getMcpService(); + if (service != null) + service.onPlayingOrderChangeReq(order); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean mediaControlPointOpcodeSupportedNative(int feature); + private native boolean mediaControlPointNative(int value); + private native boolean mediaStateNative(int state); + private native boolean mediaPlayerNameNative(String playerName); + private native boolean trackChangedNative(int status); + private native boolean trackPositionNative(int position); + private native boolean trackDurationNative(int duration); + private native boolean trackTitleNative(String title); + private native boolean playingOrderSupportedNative(int order); + private native boolean playingOrderNative(int value); + private native boolean setActiveDeviceNative(int profile, int setId, byte[] address); + private native boolean contentControlIdNative(int ccid); + private native boolean disconnectMcpNative(byte[] address); + private native boolean bondStateChangeNative(int state, byte[] address); +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java new file mode 100644 index 0000000000000000000000000000000000000000..316338296e1054510cde13f4f8519b2915b4a929 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java @@ -0,0 +1,842 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.bluetooth.mcp; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.btservice.ProfileService; +import android.bluetooth.BluetoothUuid; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.UserManager; +import android.util.Log; +import android.os.Message; +import android.os.Binder; +import android.os.IBinder; + +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.acm.AcmService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.internal.annotations.VisibleForTesting; +import android.media.session.PlaybackState; +import android.media.MediaDescription; +import android.media.MediaMetadata; +import com.android.bluetooth.apm.MediaControlManager; +import android.view.KeyEvent; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import android.media.session.MediaSessionManager; +import com.android.internal.util.ArrayUtils; +/** + * Provides Bluetooth MCP profile as a service in the Bluetooth application. + * @hide + */ +public class McpService extends ProfileService { + + private static final String TAG = "McpService"; + private static final boolean DBG = true; + public static final int MUSIC_PLAYER_CONTROL = 28; + private static McpService sMcpService; + private BroadcastReceiver mBondStateChangedReceiver; + + private BluetoothDevice mActiveDevice; + private AdapterService mAdapterService; + private McpNativeInterface mNativeInterface; + private static McpService sInstance = null; + private Context mContext; + private McsMessageHandler mHandler; + private int mMaxConnectedAudioDevices = 1; + private String mActiveMediaPlayerName = new String(""); + + public static final String ACTION_CONNECTION_STATE_CHANGED = + "com.android.bluetooth.mcp.action.CONNECTION_STATE_CHANGED"; + //native event + static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + static final int EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED = 2; + static final int EVENT_TYPE_TRACK_POSITION_CHANGED = 3; + static final int EVENT_TYPE_PLAYING_ORDER_CHANGED = 4; + //MCP to JNI update + static final int MEDIA_STATE_UPDATE = 5; + static final int MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE = 6; + static final int MEDIA_CONTROL_POINT_UPDATE = 7; + static final int MEDIA_PLAYER_NAME_UPDATE = 8; + static final int TRACK_CHANGED_UPDATE = 9; + static final int TRACK_TITLE_UPDATE = 10; + static final int TRACK_POSITION_UPDATE = 11; + static final int TRACK_DURATION_UPDATE = 12; + static final int PLAYING_ORDER_SUPPORT_UPDATE = 13; + static final int PLAYING_ORDER_UPDATE = 14; + static final int CONTENT_CONTROL_ID_UPDATE = 15; + static final int ACTIVE_DEVICE_CHANGE = 16; + static final int BOND_STATE_CHANGE = 17; + static final int MEDIA_CONTROL_MANAGER_INIT = 18; + + static final int PLAYSTATUS_ERROR = -1; + static final int PLAYSTATUS_STOPPED = 0; + static final int PLAYSTATUS_PLAYING = 1; + static final int PLAYSTATUS_PAUSED = 2; + static final int PLAYSTATUS_SEEK = 3; + + + //super set of supported player supported feature + static final int MCP_MEDIA_CONTROL_SUP_PLAY = 1<<0; + static final int MCP_MEDIA_CONTROL_SUP_PAUSE = 1<<1; + static final int MCP_MEDIA_CONTROL_SUP_FAST_REWIND = 1<<2; + static final int MCP_MEDIA_CONTROL_SUP_FAST_FORWARD = 1<<3; + static final int MCP_MEDIA_CONTROL_SUP_STOP = 1<<4; + static final int MCP_MEDIA_CONTROL_SUP_PREV_TRACK = 1<<11; + static final int MCP_MEDIA_CONTROL_SUP_NEXT_TRACK = 1<<12; + + //media control point opcodes + static final int MCP_MEDIA_CONTROL_OPCODE_PLAY = 0x01; + static final int MCP_MEDIA_CONTROL_OPCODE_PAUSE = 0x02; + static final int MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND = 0x03; + static final int MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD = 0x04; + static final int MCP_MEDIA_CONTROL_OPCODE_STOP = 0x05; + static final int MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK = 0x30; + static final int MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK = 0x31; + + //as there is not supported api to fetch player details + static final int DEFAULT_MEDIA_PLAYER_SUPPORTED_FEATURE = 0x181F; + static final int DEFAULT_PLAYER_SUPPORTED_FEATURE = 0x0001; // Single once default + static final int DEFAULT_PLAYING_ORDER = 0x01; // Single Once default + private int mState = -1; + private int mCurrOpCode = -1; + private int mSupportedControlPoint = -1; + private int mControlPoint = -1; + private int mSupportedPlayingOrder = -1; + private int mPlayingOrder = -1; + private int mCcid = -1; + private int mTrackPosition = 0xFFFF; + private int mTrackDuration = 0xFFFF; + private String mPlayerName = null; + private String mTrackTitle = null; + private MediaControlManager mMediaControlManager; + private MediaSessionManager mMediaSessionManager; + /*private class MusicPlayerDetail { + private int state; + private int featureSupported; + private int mSetFeature; + private int playingOrderFeatureSupported; + private int currentPlayingOrder; + private int ccid; + private int mTrackPosition; + private int currentTrackDuration; + private String playerName; + + public MusicPlayerDetail() { + + } + };*/ + //HashMap mMusicPlayerMap = new HashMap(); + @Override + protected IProfileServiceBinder initBinder() { + return new McpBinder(this); + } + + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + if (sMcpService != null) { + Log.w(TAG, "McpService is already running"); + return true; + } + if (DBG) { + Log.d(TAG, " Create McpService Instance"); + } + + mContext = this; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when McpService starts"); + mNativeInterface = Objects.requireNonNull(McpNativeInterface.getInstance(), + "McpNativeInterface cannot be null when McpService starts"); + // Step 2: Get maximum number of connected audio devices + mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); + Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); + //handle to synchronized tx and rx message + if (mHandler != null) { + mHandler = null; + } + HandlerThread thread = new HandlerThread("BluetoothMCSHandler"); + thread.start(); + Looper looper = thread.getLooper(); + mHandler = new McsMessageHandler(looper); + mNativeInterface.init(); + Log.d(TAG, "mcp native init done"); + IntentFilter filter = new IntentFilter(); + + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + mContext.registerReceiver(mBondStateChangedReceiver, filter); + setMcpService(this); + mMediaSessionManager = (MediaSessionManager) this.getSystemService( + this.MEDIA_SESSION_SERVICE); + //MediaControlManager.make(this); + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_CONTROL_MANAGER_INIT; + msg.obj = this; + mHandler.sendMessageDelayed(msg, 100); + return true; + } + + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sMcpService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + // Step 8: Mark service as stopped + setMcpService(null); + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + mContext.unregisterReceiver(mBondStateChangedReceiver); + // Clear AdapterService + mAdapterService = null; + mMaxConnectedAudioDevices = 1; + return true; + } + + private static void setMcpService(McpService instance) { + if (DBG) { + Log.d(TAG, "setMcpService(): set to: " + instance); + } + sMcpService = instance; + } + /** + * Get the McpService instance + * @return McpService instance + */ + + public synchronized static McpService getMcpService() { + if (sMcpService == null) { + Log.w(TAG, "getMcpService(): service is null"); + return null; + } + return sMcpService; + } + + public synchronized static void clearMcpInstance () { + Log.v(TAG, "clearing MCP instatnce"); + sInstance = null; + Log.v(TAG, "After clearing MCP instatnce "); + } + + public synchronized boolean MediaControlPointOpcodeUpdate(int feature) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE; + msg.arg1 = feature; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean MediaControlPointUpdate(int value) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_CONTROL_POINT_UPDATE; + msg.arg1 = value; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean MediaStateUpdate(int state) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_STATE_UPDATE; + msg.arg1 = state; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean MediaPlayerNameUpdate(String name) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_PLAYER_NAME_UPDATE; + msg.obj = name; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean PlayingOrderSupportedUpdate(int support) { + Message msg = mHandler.obtainMessage(); + msg.what = PLAYING_ORDER_SUPPORT_UPDATE; + msg.arg1 = support; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean PlayingOrderUpdate(int support) { + Message msg = mHandler.obtainMessage(); + msg.what = PLAYING_ORDER_UPDATE; + msg.arg1 = support; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackChangedUpdate(int status) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_CHANGED_UPDATE; + msg.arg1 = status; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackTitleUpdate(String title) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_TITLE_UPDATE; + msg.obj = title; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackDurationUpdate(int duration) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_DURATION_UPDATE; + msg.arg1 = duration; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackPositionUpdate(int position) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_POSITION_UPDATE; + msg.arg1 = position; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean ContentControlID(int ccid) { + Message msg = mHandler.obtainMessage(); + msg.what = CONTENT_CONTROL_ID_UPDATE; + msg.arg1 = ccid; + mHandler.sendMessage(msg); + return true; + } + + private synchronized int convertPlayStateToPlayStatus(PlaybackState state) { + int playStatus = PLAYSTATUS_ERROR; + switch (state.getState()) { + case PlaybackState.STATE_PLAYING: + playStatus = PLAYSTATUS_PLAYING; + break; + + case PlaybackState.STATE_CONNECTING: + case PlaybackState.STATE_NONE: + playStatus = PLAYSTATUS_STOPPED; + break; + + case PlaybackState.STATE_PAUSED: + case PlaybackState.STATE_BUFFERING: + case PlaybackState.STATE_STOPPED: + playStatus = PLAYSTATUS_PAUSED; + break; + + case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackState.STATE_SKIPPING_TO_NEXT: + case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM: + case PlaybackState.STATE_REWINDING: + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + playStatus = PLAYSTATUS_SEEK; + break; + + case PlaybackState.STATE_ERROR: + playStatus = PLAYSTATUS_ERROR; + break; + + } + return playStatus; + } + + + /** + * Get the active device. + * + * @return the active device or null if no device is active + */ + public synchronized BluetoothDevice getActiveDevice() { + return mActiveDevice; + } + + public synchronized int getControlContentID() { + int ccid = 1; + return ccid; + } + + public synchronized void onConnectionStateChanged(BluetoothDevice device, int status) { + Log.v(TAG, "onConnectionStateChanged: address=" + device.toString()); + if (status == 0) + return; + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_CONNECTION_STATE_CHANGED; + msg.obj = device; + msg.arg2 = status; + mHandler.sendMessage(msg); + return; + } + + public synchronized boolean onMediaControlPointChangeReq(BluetoothDevice device, int state) { + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED; + msg.obj = device; + msg.arg1 = state; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean onTrackPositionChangeReq(int position) { + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_TRACK_POSITION_CHANGED; + msg.arg1 = position; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean onPlayingOrderChangeReq(int order) { + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_PLAYING_ORDER_CHANGED; + msg.arg1 = order; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean SetActiveDevices(BluetoothDevice device, int profile) { + Message msg = mHandler.obtainMessage(); + msg.what = ACTIVE_DEVICE_CHANGE; + msg.obj = device; + msg.arg1 = 0; // it will use to send mark two earbud address in one gp + msg.arg2 = profile; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean OnMediaPlayerUpdate(int feature, int state, + int playingOrderSupport, int playingOrder, String playerName) { + Log.w(TAG, "OnMediaPlayerUpdate for player " + playerName); + ContentControlID(getControlContentID()); + /* + if (mMusicPlayerMap.containsKey(playerName)) { + Log.v(TAG, "Player is already there"); + } else { + newPlayer = new MusicPlayerDetail(); + mMusicPlayerMap.add(newPlayer, playerName); + }*/ + + MediaPlayerNameUpdate(playerName); + MediaStateUpdate(state); + //added default value as there is no api for gettiting supported feature from player + MediaControlPointOpcodeUpdate(DEFAULT_MEDIA_PLAYER_SUPPORTED_FEATURE); + PlayingOrderSupportedUpdate(DEFAULT_PLAYER_SUPPORTED_FEATURE); + PlayingOrderUpdate(DEFAULT_PLAYING_ORDER); + return true; + } + + public synchronized boolean OnMediaStateUpdate(int state) { + Log.w(TAG, "OnMediaStateUpdate state " + state); + MediaStateUpdate(state); + return true; + } + + public synchronized boolean OnTrackUpdate(int status, int duration, String title) { + Log.w(TAG, "OnTrackUpdate title " + title + " duration " + duration); + TrackChangedUpdate(status); + if (status != 0) { + TrackTitleUpdate(title); + TrackDurationUpdate(duration); + } + return true; + } + + public synchronized boolean OnTrackPositionUpdate(int position) { + Log.w(TAG, "OnTrackPositionUpdate position " + position); + TrackPositionUpdate(position); + return true; + } + + public synchronized boolean OnPlayingOrderUpdate(int order) { + Log.w(TAG, "OnPlayingOrderUpdate order " + order); + PlayingOrderUpdate(order); + return true; + } + //As apm is not implemented callback for apm above mention function + //implemented workaround + public synchronized void updateMetaData(MediaMetadata data) { + Log.w(TAG, "updateMetaData data " + data); + if (data == null) { + return; + } + //length //TBD to convert into int + int duration = (int)data.getLong(MediaMetadata.METADATA_KEY_DURATION); + String title = data.getString(MediaMetadata.METADATA_KEY_TITLE); + if (title != null && !(title.equals(mTrackTitle))) { + TrackChangedUpdate(1); + TrackTitleUpdate(title); + } + if (duration != mTrackDuration) + TrackDurationUpdate(duration); + } + + public synchronized void updatePlaybackState(PlaybackState playbackState) { + Log.w(TAG, "updatePlaybackState state " + playbackState); + int state = (int)convertPlayStateToPlayStatus(playbackState); + + if (state != PLAYSTATUS_ERROR && mState != state) { + if (state == PLAYSTATUS_STOPPED) + state = PLAYSTATUS_PAUSED; + MediaStateUpdate(state); + if (mCurrOpCode != -1) { + MediaControlPointUpdate(mCurrOpCode); + mCurrOpCode = -1; + } + } + int position = (int)playbackState.getPosition(); + + if (position != mTrackPosition) + TrackPositionUpdate(position); + float speed = playbackState.getPlaybackSpeed(); //for playback speed + } + + public synchronized void updatePlayerName(String packageName, boolean removed) { + Log.w(TAG, "updatePlayerName pkg " + packageName + " removed " + removed); + String name = null; + boolean changed = true; + int tCcid = 0; + int tPlayersupport = 0; + int tMediasupport = 0; + if ((removed && packageName == null ) || + removed && packageName.equals(mPlayerName)) { + //no active media player + MediaStateUpdate(PLAYSTATUS_STOPPED); + name = new String(""); + } else if (packageName != null && !packageName.equals(mPlayerName)) { + name = packageName; + tCcid = getControlContentID(); + tPlayersupport = DEFAULT_PLAYER_SUPPORTED_FEATURE; + tMediasupport = DEFAULT_MEDIA_PLAYER_SUPPORTED_FEATURE; + } else { + Log.d(TAG, "player name is same no need to update " + packageName); + changed = false; + } + if (changed) { + Log.d(TAG, "sending player change update"); + MediaControlPointOpcodeUpdate(tMediasupport); + PlayingOrderSupportedUpdate(tPlayersupport); + MediaPlayerNameUpdate(name); + ContentControlID(tCcid); + } + } + + private int McpPassthroughToKeyCode(int operation) { + mCurrOpCode = operation; + switch (operation) { + case MCP_MEDIA_CONTROL_OPCODE_PLAY: + return KeyEvent.KEYCODE_MEDIA_PLAY; + case MCP_MEDIA_CONTROL_OPCODE_PAUSE: + return KeyEvent.KEYCODE_MEDIA_PAUSE; + case MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND: + return KeyEvent.KEYCODE_MEDIA_REWIND; + case MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD: + return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; + case MCP_MEDIA_CONTROL_OPCODE_STOP: + return KeyEvent.KEYCODE_MEDIA_STOP; + case MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK: + return KeyEvent.KEYCODE_MEDIA_PREVIOUS; + case MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK: + return KeyEvent.KEYCODE_MEDIA_NEXT; + + // Fallthrough for all unknown key mappings + default: + mCurrOpCode = -1; + Log.d(TAG, "unknown passthrough"); + return KeyEvent.KEYCODE_UNKNOWN; + } + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + if (sInstance != null) + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + //update to lower layer + Message msg = mHandler.obtainMessage(); + msg.what = BOND_STATE_CHANGE; + msg.obj = device; + msg.arg1 = bondState; + mHandler.sendMessage(msg); + return; + } + private boolean isMcpOnlyDevice(BluetoothDevice device) { + ParcelUuid ASCS_UUID = + ParcelUuid.fromString("0000184E-0000-1000-8000-00805F9B34FB"); + AdapterService adapterService = AdapterService.getAdapterService(); + boolean ascsSupported = + ArrayUtils.contains(adapterService.getRemoteUuids(device), ASCS_UUID); + AcmService mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + if (ascsSupported && + mAcmService.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + return false; + } + } + Log.d(TAG,"McpOnly device"); + return true; + } + /** Handles MCS messages. */ + private final class McsMessageHandler extends Handler { + private McsMessageHandler(Looper looper) { + super(looper); + } + @Override + public synchronized void handleMessage(Message msg) { + if (DBG) Log.v(TAG, "McsMessageHandler: received message=" + msg.what); + + switch (msg.what) { + + case ACTIVE_DEVICE_CHANGE: + if (DBG) Log.v(TAG, "ACTIVE_DEVICE_CHANGE msg: " + (BluetoothDevice)msg.obj + " msg2 : " + msg.arg1); + mActiveDevice = (BluetoothDevice)msg.obj; + mNativeInterface.setActiveDevice((BluetoothDevice)msg.obj, msg.arg1, msg.arg2); + break; + + case BOND_STATE_CHANGE: + if (DBG) Log.v(TAG, "BOND_STATE_CHANGE msg: " + (BluetoothDevice)msg.obj + " msg2 : " + msg.arg1); + mNativeInterface.bondStateChange((BluetoothDevice)msg.obj, msg.arg2); + break; + + case PLAYING_ORDER_SUPPORT_UPDATE: + if (DBG) Log.v(TAG, "PLAYING_ORDER_SUPPORT_UPDATE msg: " + msg.arg1); + mSupportedPlayingOrder = msg.arg1; + mNativeInterface.playingOrder(msg.arg1); + break; + + case PLAYING_ORDER_UPDATE: + if (DBG) Log.v(TAG, "PLAYING_ORDER_UPDATE msg: " + msg.arg1); + mPlayingOrder = msg.arg1; + mNativeInterface.playingOrder(msg.arg1); + break; + + case MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE msg: " + msg.arg1); + mSupportedControlPoint = msg.arg1; + mNativeInterface.mediaControlPointOpcodeSupported(msg.arg1); + break; + + case MEDIA_CONTROL_POINT_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_CONTROL_POINT_UPDATE msg: " + msg.arg1); + mControlPoint = msg.arg1; + mNativeInterface.mediaControlPoint(msg.arg1); + break; + + case MEDIA_PLAYER_NAME_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_PLAYER_NAME_UPDATE msg: " + (String)msg.obj); + String name = (String)msg.obj; + mPlayerName = name; + if (name == null) + name = new String(""); + mNativeInterface.mediaPlayerName(name); + break; + + case MEDIA_STATE_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_STATE_UPDATE msg: " + msg.arg1); + mState = msg.arg1; + mNativeInterface.mediaState(msg.arg1); + break; + + case TRACK_CHANGED_UPDATE: + if (DBG) Log.v(TAG, "TRACK_CHANGED_UPDATE msg: " + msg.arg1); + mNativeInterface.trackChanged(msg.arg1); + break; + + case TRACK_DURATION_UPDATE: + if (DBG) Log.v(TAG, "TRACK_DURATION_UPDATE msg: " + msg.arg1); + mTrackDuration = msg.arg1; + mNativeInterface.trackDuration(msg.arg1); + break; + + case TRACK_POSITION_UPDATE: + if (DBG) Log.v(TAG, "TRACK_POSITION_UPDATE msg: " + msg.arg1); + mTrackPosition = msg.arg1; + mNativeInterface.trackPosition(msg.arg1); + break; + + case TRACK_TITLE_UPDATE: + if (DBG) Log.v(TAG, "TRACK_TITLE_UPDATE msg: " + (String)msg.obj); + String title = (String)msg.obj; + mTrackTitle = title; + mNativeInterface.trackTitle(title); + break; + + + case CONTENT_CONTROL_ID_UPDATE: + if (DBG) Log.v(TAG, "CONTENT_CONTROL_ID_UPDATE msg: " + msg.arg1); + mCcid = msg.arg1; + mNativeInterface.contentControlId(mCcid); + break; + + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_CONNECTION_STATE_CHANGED msg: " + msg.arg1); + //update to APM + break; + + case EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED msg: " + msg.arg1); + BluetoothDevice mMcpDevice = (BluetoothDevice)msg.obj; + int code = McpPassthroughToKeyCode(msg.arg1); + + if (code != KeyEvent.KEYCODE_UNKNOWN) { + Log.w(TAG, "Valid passthrough, dispatch to media player"); + } + if (code == KeyEvent.KEYCODE_MEDIA_PLAY && + !Objects.equals(mMcpDevice, mActiveDevice) && !isMcpOnlyDevice(mMcpDevice)) { + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + if (mActiveDeviceManager != null) { + mActiveDeviceManager.setActiveDevice(mMcpDevice, ApmConst.AudioFeatures.MEDIA_AUDIO, false, true); + } + } + + // WAR- For FF/Rewind UC + if (code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || + code == KeyEvent.KEYCODE_MEDIA_REWIND) { + if (mState != PLAYSTATUS_SEEK) { + mState = PLAYSTATUS_SEEK; + MediaStateUpdate(mState); + MediaControlPointUpdate(mCurrOpCode); + mCurrOpCode = -1; + Log.w(TAG, "Update Playstate as seeking for FF/Rewind opcode"); + } + } else { + if (mState == PLAYSTATUS_SEEK) { // To-Do + } + } + + KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, code); + mMediaSessionManager.dispatchMediaKeyEvent(event, false); + event = new KeyEvent(KeyEvent.ACTION_UP, code); + mMediaSessionManager.dispatchMediaKeyEvent(event, false); + break; + + case EVENT_TYPE_PLAYING_ORDER_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_PLAYING_ORDER_CHANGED msg: " + msg.arg1); + //update to APM + break; + + case EVENT_TYPE_TRACK_POSITION_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_TRACK_POSITION_CHANGED msg: " + msg.arg1); + //update to APM + break; + + case MEDIA_CONTROL_MANAGER_INIT: + if (DBG) Log.v(TAG, "MEDIA_CONTROL_MANAGER_INIT"); + Context context = (Context)msg.obj; + MediaControlManager.make(context); + break; + + default: + Log.e(TAG, "unknown message! msg.what=" + msg.what); + break; + } + Log.v(TAG, "Exit handleMessage"); + } + } + + /** + * Binder object: must be a static class or memory leak may occur. + */ + + static class McpBinder extends Binder implements IProfileServiceBinder { + private McpService mService; + + private McpService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + McpBinder(McpService svc) { + mService = svc; + } + + @Override + public synchronized void cleanup() { + mService = null; + } + } +} + + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java new file mode 100644 index 0000000000000000000000000000000000000000..f0847716f341415bdd9a1424b7132ee7b301c9da --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.pc; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.UserManager; +import android.util.Log; + +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +public class PCService extends ProfileService{ + private static final String TAG = "PCService"; + private static final boolean DBG = true; + private static final int MAX_PACS_STATE_MACHINES = 50; + + private HandlerThread mStateMachinesThread; + private final HashMap mStateMachines = + new HashMap<>(); + private BroadcastReceiver mBondStateChangedReceiver; + + private AdapterService mAdapterService; + private PacsClientNativeInterface mNativeInterface; + private static PCService sInstance = null; + + public static final String ACTION_CONNECTION_STATE_CHANGED = + "com.android.bluetooth.pacs.action.CONNECTION_STATE_CHANGED"; + + /** + * Get the PCService instance. Returns null if the service hasn't been initialized. + */ + public static PCService get() { + return sInstance; + } + + @Override + protected IProfileServiceBinder initBinder() { + return null; + } + + @Override + protected void create() { + if (DBG) { + Log.d(TAG, "create()"); + } + } + + protected boolean start() { + + if (DBG) { + Log.d(TAG, "start()"); + } + if (sInstance != null) { + Log.w(TAG, "PCService is already running"); + return true; + } + + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when PCService starts"); + mNativeInterface = Objects.requireNonNull(PacsClientNativeInterface.getInstance(), + "PacsClientNativeInterface cannot be null when PCService starts"); + + // Start handler thread for state machines + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("PCService.StateMachines"); + mStateMachinesThread.start(); + mNativeInterface.init(); + sInstance = this; + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + registerReceiver(mBondStateChangedReceiver, filter); + + return true; + } + + @Override + protected boolean stop() { + if (DBG) { + Log.d(TAG, "stop()"); + } + if (sInstance == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + + unregisterReceiver(mBondStateChangedReceiver); + + // Mark service as stopped + sInstance = null; + + // Destroy state machines and stop handler thread + synchronized (mStateMachines) { + for (PacsClientStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + + if (mStateMachinesThread != null) { + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + } + + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + + // Clear AdapterService + mAdapterService = null; + return true; + } + + @Override + protected void cleanup() { + if (DBG) { + Log.d(TAG, "cleanup()"); + } + } + + /** + * Get the PCService instance + * @return PCService instance + */ + public static synchronized PCService getPCService() { + if (sInstance == null) { + Log.w(TAG, "getPCService(): service is NULL"); + return null; + } + + return sInstance; + } + + /** + * Connects the pacs profile to the passed in device + * + * @param device is the device with which we will connect the pacs profile + * @return true if pacs profile successfully connected, false otherwise + */ + + public boolean connect(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "connect(): " + device); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + PacsClientStateMachine smConnect = getOrCreateStateMachine(device); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + device + " : no state machine"); + return false; + } + smConnect.sendMessage(PacsClientStateMachine.CONNECT); + } + + return true; + } + + /** + * Disconnects pacs profile for the passed in device + * + * @param device is the device with which we want to disconnected the pacs profile + * @return true if pacs profile successfully disconnected, false otherwise + */ + + public boolean disconnect(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "disconnect(): " + device); + } + if (device == null) { + return false; + } + synchronized (mStateMachines) { + PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting"); + return false; + } + int connectionState = stateMachine.getConnectionState(); + if (connectionState != BluetoothProfile.STATE_CONNECTED + && connectionState != BluetoothProfile.STATE_CONNECTING) { + Log.w(TAG, "disconnect: device " + device + + " not connected/connecting, connectionState=" + connectionState); + return false; + } + stateMachine.sendMessage(PacsClientStateMachine.DISCONNECT); + } + return true; + } + + /** + * start pacs disocvery for the passed in device + * + * @param device is the device with which we want to dicscoer the pacs + * @return true if pacs discovery is successfull, false otherwise + */ + + public boolean startPacsDiscovery(BluetoothDevice device) { + synchronized (mStateMachines) { + Log.i(TAG, "startPacsDiscovery: device=" + device + ", " + Utils.getUidPidString()); + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "startPacsDiscovery: device " + device + " was never connected/connecting"); + return false; + } + if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "startPacsDiscovery: profile not connected"); + return false; + } + stateMachine.sendMessage(PacsClientStateMachine.START_DISCOVERY); + } + return true; + } + + /** + * get sink pacs for the passed in device + * + * @param device is the device with which we want to get sink pacs + * @return sink pacs + */ + + public BluetoothCodecConfig[] getSinkPacs(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get Sink Pacs"); + return null; + } + return stateMachine.getSinkPacs(); + } + } + + /** + * get src pacs for the passed in device + * + * @param device is the device with which we want to get src pacs + * @return src pacs + */ + + public BluetoothCodecConfig[] getSrcPacs(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get Src Pacs"); + return null; + } + return stateMachine.getSinkPacs(); + } + } + + /** + * get sink locations for the passed in device + * + * @param device is the device with which we want to get sink location + * @return sink locations + */ + + public int getSinklocations(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get sink locations"); + return -1; + } + return stateMachine.getSinklocations(); + } + } + + /** + * get src locations for the passed in device + * + * @param device is the device with which we want to get src location + * @return src locations + */ + + public int getSrclocations(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get src locations"); + return -1; + } + return stateMachine.getSrclocations(); + } + } + + /** + * get available contexts for the passed in device + * + * @param device is the device with which we want to get available contexts + * @return avaialable contexts + */ + + public int getAvailableContexts(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get available contexts"); + return -1; + } + return stateMachine.getAvailableContexts(); + } + } + + /** + * get supported contexts for the passed in device + * + * @param device is the device with which we want to get supported contexts + * @return supported contexts + */ + + public int getSupportedContexts(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get supported contexts"); + return -1; + } + return stateMachine.getSupportedContexts(); + } + } + + /** + * Get the current connection state of the profile + * + * @param device is the remote bluetooth device + * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, + * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, + * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or + * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected + */ + public int getConnectionState(BluetoothDevice device) { + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean okToConnect(BluetoothDevice device) { + + int bondState = mAdapterService.getBondState(device); + if (bondState != BluetoothDevice.BOND_BONDED) { + Log.w(TAG, "okToConnect: return false, bondState=" + bondState); + return false; + } + return true; + } + + void messageFromNative(PacsClientStackEvent stackEvent) { + Objects.requireNonNull(stackEvent.device, + "Device should never be null, event: " + stackEvent); + + synchronized (mStateMachines) { + BluetoothDevice device = stackEvent.device; + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + if (stackEvent.type == PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { + switch (stackEvent.valueInt1) { + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + sm = getOrCreateStateMachine(device); + break; + default: + break; + } + } + } + if (sm == null) { + Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); + return; + } + sm.sendMessage(PacsClientStateMachine.STACK_EVENT, stackEvent); + } + } + + void onConnectionStateChangedFromStateMachine(BluetoothDevice device, + int newState, int prevState) { + Log.d(TAG, "onConnectionStateChangedFromStateMachine for device: " + device + + " newState: " + newState); + + synchronized (mStateMachines) { + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + int bondState = mAdapterService.getBondState(device); + if (bondState == BluetoothDevice.BOND_NONE) { + removeStateMachine(device); + } + } else if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "PacsClient get connected with renderer device: " + device); + } + } + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + return; + } + removeStateMachine(device); + } + } + + private void removeStateMachine(BluetoothDevice device) { + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.w(TAG, "removeStateMachine: device " + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.doQuit(); + sm.cleanup(); + mStateMachines.remove(device); + } + } + + private PacsClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + if (mStateMachines.size() >= MAX_PACS_STATE_MACHINES) { + Log.e(TAG, "Maximum number of PACS state machines reached: " + + MAX_PACS_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = PacsClientStateMachine.make(device, this, + mNativeInterface, mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..b7a4c516b15ccb4361bb7e5d047ba1d0c4f1b0f0 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Defines the native interface that is used by state machine/service to + * send or receive messages from the native stack. This file is registered + * for the native methods in the corresponding JNI C++ file. + */ +package com.android.bluetooth.pc; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothCodecConfig; +import android.util.Log; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * PacsClient Native Interface to/from JNI. + */ +public class PacsClientNativeInterface { + private static final String TAG = "PacsClientNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + private int pacs_client_id = -1; + + @GuardedBy("INSTANCE_LOCK") + private static PacsClientNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private PacsClientNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtf(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static PacsClientNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new PacsClientNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(pacs_client_id); + pacs_client_id = -1; + } + + /** + * Initiates PacsClient connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean connectPacsClient(BluetoothDevice device) { + return connectPacsClientNative(pacs_client_id, getByteAddress(device)); + } + + /** + * Disconnects PacsClient from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectPacsClient(BluetoothDevice device) { + return disconnectPacsClientNative(pacs_client_id, getByteAddress(device)); + } + + /** + * Trigger service discovery for pacs + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean startDiscoveryNative(BluetoothDevice device) { + return startDiscoveryNative(pacs_client_id, getByteAddress(device)); + } + + /** + * get available audio contexts. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean GetAvailableAudioContexts(BluetoothDevice device) { + return GetAvailableAudioContextsNative(pacs_client_id, getByteAddress(device)); + } + + private BluetoothDevice getDevice(byte[] address) { + if (mAdapter != null) { + return mAdapter.getRemoteDevice(address); + } else { + return null; + } + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + private void sendMessageToService(PacsClientStackEvent event) { + PCService service = PCService.getPCService(); + if (service != null) { + service.messageFromNative(event); + } else { + Log.e(TAG, "Event ignored, service not available: " + event); + } + } + + // Callbacks from the native stack back into the Java framework. + // All callbacks are routed via the Service which will disambiguate which + // state machine the message should be routed to. + + private void OnInitialized(int state, int client_id) { + pacs_client_id = client_id; + } + + private void onConnectionStateChanged(byte[] address, int state) { + PacsClientStackEvent event = + new PacsClientStackEvent(PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + + if (DBG) { + Log.d(TAG, "onConnectionStateChanged: " + event); + } + sendMessageToService(event); + } + + private void OnAudioContextAvailable(byte[] address, int available_contexts) { + PacsClientStackEvent event = + new PacsClientStackEvent(PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL); + event.device = getDevice(address); + event.valueInt1 = available_contexts; + + if (DBG) { + Log.d(TAG, "OnAudioContextAvailable: " + event); + } + sendMessageToService(event); + } + + private void onServiceDiscovery(BluetoothCodecConfig[] sink_pacs_array, + BluetoothCodecConfig[] src_pacs_array, + int sink_locations, int src_locations, + int available_contexts, int supported_contexts, + int status, byte[] address) { + if (status != 0) { + Log.e(TAG, "onServiceDiscovery: Failed" + status); + return; + } + PacsClientStackEvent event = new PacsClientStackEvent( + PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY); + event.device = getDevice(address); + event.sinkCodecConfig = sink_pacs_array; + event.srcCodecConfig = src_pacs_array; + event.valueInt1 = sink_locations; + event.valueInt2 = src_locations; + event.valueInt3 = available_contexts; + event.valueInt4 = supported_contexts; + if (DBG) { + Log.d(TAG, "onServiceDiscovery: " + event); + } + for (BluetoothCodecConfig codecConfig : + sink_pacs_array) { + Log.d(TAG, "sink_pacs_array: " + codecConfig); + } + for (BluetoothCodecConfig codecConfig : + src_pacs_array) { + Log.d(TAG, "src_pacs_array: " + codecConfig); + } + if (DBG) { + Log.d(TAG, "sink locs: " + sink_locations + "src locs:" + src_locations); + Log.d(TAG, "avail ctxts: " + available_contexts + "supp ctxts: " + supported_contexts); + } + + sendMessageToService(event); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(int client_id); + private native boolean connectPacsClientNative(int client_id, byte[] address); + private native boolean disconnectPacsClientNative(int client_id, byte[] address); + private native boolean startDiscoveryNative(int client_id, byte[] address); + private native boolean GetAvailableAudioContextsNative(int client_id, byte[] address); +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..ff1be613fd57a3eacbb85c4983e5aa39ec7cc3e8 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.pc; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothCodecConfig; + + +/** + * Stack event sent via a callback from JNI to Java, or generated + * internally by the Pacs Cleint State Machine. + */ +public class PacsClientStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_INITIALIZED = 1; + public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 2; + public static final int EVENT_TYPE_SERVICE_DISCOVERY = 3; + public static final int EVENT_TYPE_AUDIO_CONTEXT_AVAIL = 4; + + // Do not modify without updating the HAL bt_pacs_client.h files. + // Match up with enum class ConnectionState of bt_pacs_client.h. + static final int CONNECTION_STATE_DISCONNECTED = 0; + static final int CONNECTION_STATE_CONNECTING = 1; + static final int CONNECTION_STATE_CONNECTED = 2; + static final int CONNECTION_STATE_DISCONNECTING = 3; + + public int type; + public BluetoothDevice device; + public BluetoothCodecConfig[] sinkCodecConfig; + public BluetoothCodecConfig[] srcCodecConfig; + public int valueInt1; + public int valueInt2; + public int valueInt3; + public int valueInt4; + + PacsClientStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("PacsClientStackEvent {type:" + eventTypeToString(type)); + result.append(", device:" + device); + result.append(", value1:" + valueInt1); + result.append(", value2:" + valueInt2); + result.append(", value3:" + valueInt3); + result.append(", value4:" + valueInt4); + if (sinkCodecConfig != null) { + result.append(", sinkCodecConfig:" + sinkCodecConfig); + } + if (srcCodecConfig != null) { + result.append(", srcCodecConfig:" + srcCodecConfig); + } + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + return "EVENT_TYPE_CONNECTION_STATE_CHANGED"; + case EVENT_TYPE_INITIALIZED: + return "EVENT_TYPE_INITIALIZED"; + case EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + return "EVENT_TYPE_AUDIO_CONTEXT_AVAIL"; + case EVENT_TYPE_SERVICE_DISCOVERY: + return "EVENT_TYPE_SERVICE_DISCOVERY"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java new file mode 100644 index 0000000000000000000000000000000000000000..23ff16eac5b54b111dac5b934e49ecf1efb9a3d6 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java @@ -0,0 +1,703 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Bluetooth PacsClient StateMachine. There is one instance per remote device. + * - "Disconnected" and "Connected" are steady states. + * - "Connecting" and "Disconnecting" are transient states until the + * connection / disconnection is completed. + * + * + * (Disconnected) + * | ^ + * CONNECT | | DISCONNECTED + * V | + * (Connecting)<--->(Disconnecting) + * | ^ + * CONNECTED | | DISCONNECT + * V | + * (Connected) + * NOTES: + * - If state machine is in "Connecting" state and the remote device sends + * DISCONNECT request, the state machine transitions to "Disconnecting" state. + * - Similarly, if the state machine is in "Disconnecting" state and the remote device + * sends CONNECT request, the state machine transitions to "Connecting" state. + * + * DISCONNECT + * (Connecting) ---------------> (Disconnecting) + * <--------------- + * CONNECT + * + */ + +package com.android.bluetooth.pc; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothCodecConfig; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.content.Context; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; + +final class PacsClientStateMachine extends StateMachine { + private static final boolean DBG = false; + private static final String TAG = "PacsClientStateMachine"; + + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int START_DISCOVERY = 3; + static final int GET_AVAILABLE_CONTEXTS = 4; + @VisibleForTesting + static final int STACK_EVENT = 101; + private static final int CONNECT_TIMEOUT = 201; + + // NOTE: the value is not "final" - it is modified in the unit tests + @VisibleForTesting + static int sConnectTimeoutMs = 30000; // 30s + + private Disconnected mDisconnected; + private Connecting mConnecting; + private Disconnecting mDisconnecting; + private Connected mConnected; + private int mLastConnectionState = -1; + + private PCService mService; + private PacsClientNativeInterface mNativeInterface; + private BluetoothCodecConfig[] mSinkPacsConfig; + private BluetoothCodecConfig[] mSrcPacsConfig; + private int mSinkLocations; + private int mSrcLocations; + private int mAvailableContexts; + private int mSupportedContexts; + private Context mContext; + + private final BluetoothDevice mDevice; + + PacsClientStateMachine(BluetoothDevice device, PCService svc, + PacsClientNativeInterface nativeInterface, Looper looper) { + super(TAG, looper); + mDevice = device; + mService = svc; + mNativeInterface = nativeInterface; + + mDisconnected = new Disconnected(); + mConnecting = new Connecting(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + + addState(mDisconnected); + addState(mConnecting); + addState(mDisconnecting); + addState(mConnected); + + setInitialState(mDisconnected); + } + + static PacsClientStateMachine make(BluetoothDevice device, PCService svc, + PacsClientNativeInterface nativeInterface, Looper looper) { + Log.i(TAG, "make for device " + device); + PacsClientStateMachine PacsClientSm = new PacsClientStateMachine(device, svc, + nativeInterface, looper); + PacsClientSm.start(); + return PacsClientSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + } + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + + removeDeferredMessages(DISCONNECT); + + if (mLastConnectionState != -1) { + // Don't broadcast during startup + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED, + mLastConnectionState); + } + cleanupDevice(); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + log("Connecting to " + mDevice); + if (!mNativeInterface.connectPacsClient(mDevice)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + if (mService.okToConnect(mDevice)) { + transitionTo(mConnecting); + } else { + // Reject the request and stay in Disconnected state + Log.w(TAG, "Outgoing PacsClient Connecting request rejected: " + mDevice); + } + break; + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + if (DBG) { + Log.d(TAG, "Disconnected: stack event: " + event); + } + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_INITIALIZED: + if(event.valueInt1 != 0) { + Log.e(TAG, "Disconnected: error initializing PACS"); + return NOT_HANDLED; + } + Log.d(TAG, "PACS Initialized succesfully (DISCONNECTED)"); + break; + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnected state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Ignore PacsClient DISCONNECTED event: " + mDevice); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + if (mService.okToConnect(mDevice)) { + Log.i(TAG, "Incoming PacsClient Connecting request accepted: " + mDevice + + "state: " + state); + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming PacsClient Connecting request rejected: " + mDevice + + "state: " + state); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + Log.w(TAG, "PacsClient Connected from Disconnected state: " + mDevice + + "state: " + state); + if (mService.okToConnect(mDevice)) { + Log.i(TAG, "Incoming PacsClient Connected request accepted: " + mDevice + + "state: " + state); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming PacsClient Connected request rejected: " + mDevice + + "state: " + state); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Ignore PacsClient DISCONNECTING event: " + mDevice + + "state: " + state); + break; + default: + Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice + + "state: " + state); + break; + } + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "Connecting connection timeout: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + PacsClientStackEvent disconnectEvent = + new PacsClientStackEvent( + PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + disconnectEvent.device = mDevice; + disconnectEvent.valueInt1 = PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED; + sendMessage(STACK_EVENT, disconnectEvent); + break; + case DISCONNECT: + log("Connecting: connection canceled to " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + transitionTo(mDisconnected); + break; + case START_DISCOVERY: + case GET_AVAILABLE_CONTEXTS: + deferMessage(message); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + log("Connecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_INITIALIZED: + if(event.valueInt1 != 0) { + Log.e(TAG, "Disconnected: error initializing PACS"); + return NOT_HANDLED; + } + Log.d(TAG, "PACS Initialized succesfully (CONNECTING)"); + break; + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY: + case PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + deferMessage(message); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connecting state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Connecting device disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + transitionTo(mConnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Disconnecting connection timeout: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + PacsClientStackEvent disconnectEvent = + new PacsClientStackEvent( + PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + disconnectEvent.device = mDevice; + disconnectEvent.valueInt1 = PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED; + sendMessage(STACK_EVENT, disconnectEvent); + break; + } + case START_DISCOVERY: + case GET_AVAILABLE_CONTEXTS: + case DISCONNECT: + deferMessage(message); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + log("Disconnecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY: + case PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + deferMessage(message); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnecting state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + if (mService.okToConnect(mDevice)) { + Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming PacsClient Connected request rejected: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + if (mService.okToConnect(mDevice)) { + Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice); + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming PacsClient Connecting request rejected: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + removeDeferredMessages(CONNECT); + mNativeInterface.startDiscoveryNative(mDevice); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); + break; + case DISCONNECT: + log("Disconnecting from " + mDevice); + if (!mNativeInterface.disconnectPacsClient(mDevice)) { + // If error in the native stack, transition directly to Disconnected state. + Log.e(TAG, "Connected: error disconnecting from " + mDevice); + transitionTo(mDisconnected); + break; + } + transitionTo(mDisconnecting); + break; + case START_DISCOVERY: + log("sending start discovery to " + mDevice); + if (!mNativeInterface.startDiscoveryNative(mDevice)) { + Log.e(TAG, "connected: error sending startdiscovery to " + mDevice); + } + break; + case GET_AVAILABLE_CONTEXTS: + log("get available audio conxtes from " + mDevice); + mNativeInterface.GetAvailableAudioContexts(mDevice); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + log("Connected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_INITIALIZED: + deferMessage(message); + break; + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY: + processPacsRecordEvent(event.sinkCodecConfig, event.srcCodecConfig, + event.valueInt1, event.valueInt2, + event.valueInt3, event.valueInt4); + break; + case PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + mAvailableContexts = event.valueInt1; + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connected state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected from " + mDevice); + transitionTo(mDisconnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.i(TAG, "Disconnecting from " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state); + break; + } + } + + private void processPacsRecordEvent(BluetoothCodecConfig[] sinkCodecConfig, + BluetoothCodecConfig[] srcCodecConfig, + int sink_locations, int src_locations, + int available_contexts, int supported_contexts) { + mSinkPacsConfig = sinkCodecConfig; + mSrcPacsConfig = srcCodecConfig; + mSinkLocations = sink_locations; + mSrcLocations = src_locations; + mAvailableContexts = available_contexts; + mSupportedContexts = supported_contexts; + } + } + + int getConnectionState() { + String currentState = getCurrentState().getName(); + switch (currentState) { + case "Disconnected": + return BluetoothProfile.STATE_DISCONNECTED; + case "Connecting": + return BluetoothProfile.STATE_CONNECTING; + case "Connected": + return BluetoothProfile.STATE_CONNECTED; + case "Disconnecting": + return BluetoothProfile.STATE_DISCONNECTING; + default: + Log.e(TAG, "Bad currentState: " + currentState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + BluetoothDevice getDevice() { + return mDevice; + } + + synchronized boolean isConnected() { + return getCurrentState() == mConnected; + } + + + private void cleanupDevice() { + log("cleanup device " + mDevice); + mSinkLocations = -1; + mSrcLocations = -1; + mAvailableContexts = -1; + mSupportedContexts = -1; + } + + BluetoothCodecConfig[] getSinkPacs() { + synchronized (this) { + return mSinkPacsConfig; + } + } + + BluetoothCodecConfig[] getSrcPacs() { + synchronized (this) { + return mSrcPacsConfig; + } + } + + int getSinklocations() { + synchronized (this) { + return mSinkLocations; + } + } + + int getSrclocations() { + synchronized (this) { + return mSrcLocations; + } + } + + int getAvailableContexts() { + synchronized (this) { + return mAvailableContexts; + } + } + + int getSupportedContexts() { + synchronized (this) { + return mSupportedContexts; + } + } + + // This method does not check for error condition (newState == prevState) + private void broadcastConnectionState(int newState, int prevState) { + log("Connection state " + mDevice + ": " + profileStateToString(prevState) + + "->" + profileStateToString(newState)); + mService.onConnectionStateChangedFromStateMachine(mDevice, newState, prevState); + Intent intent = new Intent(PCService.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case STACK_EVENT: + return "STACK_EVENT"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + default: + break; + } + return Integer.toString(what); + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + @Override + protected void log(String msg) { + if (DBG) { + super.log(msg); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java new file mode 100644 index 0000000000000000000000000000000000000000..ab29aa0317812deac66962e37f26f2d7e8495d1f --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java @@ -0,0 +1,733 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.vcp; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothVcp; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.UserManager; +import android.util.Log; + +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.apm.DeviceProfileMap; +import com.android.bluetooth.apm.VolumeManager; +import com.android.bluetooth.acm.AcmService; +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.bluetooth.groupclient.GroupService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +public class VcpController { + private static final String TAG = "VcpController"; + private static final boolean DBG = true; + private static final int MAX_VCP_STATE_MACHINES = 50; + private static final int VCP_MIN_VOL = 0; + private static final int VCP_MAX_VOL = 255; + private static final String ACTION_CONNECT_DEVICE = + "com.android.bluetooth.vcp.test.action.CONNECT_DEVICE"; + private static final String ACTION_DISCONNECT_DEVICE = + "com.android.bluetooth.vcp.test.action.DISCONNECT_DEVICE"; + + private HandlerThread mStateMachinesThread; + private final HashMap mStateMachines = + new HashMap<>(); + private HashMap mConnectionMode = new HashMap(); + private BroadcastReceiver mBondStateChangedReceiver; + + private AdapterService mAdapterService; + private VcpControllerNativeInterface mNativeInterface; + private DeviceProfileMap mDpm; + private AcmService mAcmService; + private static VcpController sInstance = null; + private Context mContext; + private boolean mPtsTest = false; + private final BroadcastReceiver mVcpControllerTestReceiver = new VcpControllerTestReceiver(); + + private VcpController(Context context) { + if (DBG) { + Log.d(TAG, "Create VcpController Instance"); + } + + mContext = context; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when VcpController starts"); + mNativeInterface = Objects.requireNonNull(VcpControllerNativeInterface.getInstance(), + "VcpControllerNativeInterface cannot be null when VcpController starts"); + + // Start handler thread for state machines + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("VcpController.StateMachines"); + mStateMachinesThread.start(); + mNativeInterface.init(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + mContext.registerReceiver(mBondStateChangedReceiver, filter); + + if (mAdapterService.isAdvBCAAudioFeatEnabled()) { + Log.d(TAG, "Adv BCA Audio supported, enable VCP for broadcast"); + SystemProperties.set("persist.vendor.service.bt.vcpForBroadcast", "true"); + } else { + SystemProperties.set("persist.vendor.service.bt.vcpForBroadcast", "false"); + } + mPtsTest = SystemProperties.getBoolean("persist.vendor.service.bt.vcp_controller.pts", false); + if (mPtsTest) { + Log.d(TAG, "Register for VcpControllerTestReceiver"); + IntentFilter filter2 = new IntentFilter(); + filter2.addAction(ACTION_CONNECT_DEVICE); + filter2.addAction(ACTION_DISCONNECT_DEVICE); + context.registerReceiver(mVcpControllerTestReceiver, filter2); + } + } + + /** + * Make VcpController instance and Initialize + * + * @param context: application context + * @return VcpController instance + */ + public static VcpController make(Context context) { + Log.v(TAG, "make"); + + if(sInstance == null) { + sInstance = new VcpController(context); + } + Log.v(TAG, "Exit make"); + return sInstance; + } + + /** + * Get the VcpController instance, which provides the public APIs + * to volume control operation via VCP connection + * + * @return VcpController instance + */ + public static synchronized VcpController getVcpController() { + if (sInstance == null) { + Log.w(TAG, "getVcpController(): service is NULL"); + return null; + } + + return sInstance; + } + + public static void clearVcpInstance () { + Log.v(TAG, "clearing VCP instatnce"); + sInstance = null; + Log.v(TAG, "After clearing VCP instatnce "); + } + + public synchronized void doQuit() { + if (DBG) { + Log.d(TAG, "doQuit()"); + } + if (sInstance == null) { + Log.w(TAG, "doQuit() called before make()"); + return; + } + + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + mContext.unregisterReceiver(mBondStateChangedReceiver); + if (mPtsTest) { + mContext.unregisterReceiver(mVcpControllerTestReceiver); + } + + // Mark service as stopped + sInstance = null; + + // Destroy state machines and stop handler thread + synchronized (mStateMachines) { + for (VcpControllerStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + + if (mStateMachinesThread != null) { + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + } + + // Clear AdapterService + mAdapterService = null; + } + + /** + * Connect with the remote device for unicast or broadcast mode. + * + * @param device: the remote device to connect + * @param mode: connection mode: can be any of + * {@link #BluetoothVcp.MODE_UNICAST} or {@link #BluetoothVcp.MODE_BROADCAST} + * + * @return true if connect is accepted, false if connect request is rejected. + */ + public boolean connect(BluetoothDevice device, int mode) { + if (DBG) { + Log.d(TAG, "connect(): " + device + ", mode: " + mode); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + VcpControllerStateMachine smConnect = getOrCreateStateMachine(device); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + device + " : no state machine"); + } + + int preConnMode; + if (mConnectionMode.containsKey(device)) { + preConnMode = mConnectionMode.get(device); + if ((preConnMode & mode) == 0) { + int connMode = preConnMode | mode; + mConnectionMode.put(device, connMode); + broadcastConnectionModeChanged(device, connMode); + } + } else { + preConnMode = BluetoothVcp.MODE_NONE; + mConnectionMode.put(device, mode); + broadcastConnectionModeChanged(device, mode); + } + + if (smConnect.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + smConnect.sendMessage(VcpControllerStateMachine.CONNECT, device); + } else { + if (preConnMode == BluetoothVcp.MODE_BROADCAST && + mode == BluetoothVcp.MODE_UNICAST) { + Log.d(TAG, "VCP connection from BROADCAST-ONLY to UNICAST_BROADCAST: " + device); + } + } + } + + return true; + } + + /** + * Disconnect with the remote device for unicast or broadcast mode. + * + * @param device: the remote device to connect + * @param mode: connection mode: can be any of + * {@link #BluetoothVcp.MODE_UNICAST} or {@link #BluetoothVcp.MODE_BROADCAST} + * + * @return true if disconnect is accepted, false if disconnect is rejected. + */ + public boolean disconnect(BluetoothDevice device, int mode) { + if (DBG) { + Log.d(TAG, "disconnect(): " + device + ", mode: " + mode); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + int preConnMode = getConnectionMode(device); + int connMode = BluetoothVcp.MODE_NONE; + + if ((preConnMode & mode) != 0) { + connMode = preConnMode & ~mode; + broadcastConnectionModeChanged(device, connMode); + } else { + Log.d(TAG, "disconnect ignore as Vcp is not connected for mode: " + mode); + return false; + } + + if (connMode == BluetoothVcp.MODE_NONE) { + mConnectionMode.remove(device); + VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting"); + return false; + } + int connectionState = stateMachine.getConnectionState(); + if (connectionState != BluetoothProfile.STATE_CONNECTED + && connectionState != BluetoothProfile.STATE_CONNECTING) { + Log.w(TAG, "disconnect: device " + device + + " not connected/connecting, connectionState=" + connectionState); + return false; + } + stateMachine.sendMessage(VcpControllerStateMachine.DISCONNECT, device); + } else { + mConnectionMode.put(device, connMode); + } + } + return true; + } + + /** + * Set absolute volume to remote device via VCP connection + * + * @param device: remote device instance + * @param volume: requested volume settings for remote device + * @return true if set abs volume requst is accepted, false if set + * abs volume request is rejected + */ + public boolean setAbsoluteVolume(BluetoothDevice device, int volume, int audioType) { + synchronized (mStateMachines) { + Log.i(TAG, "setAbsVolume: device=" + device + ", " + Utils.getUidPidString()); + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + + if (stateMachine == null) { + Log.w(TAG, "setAbsVolume: device " + device + " was never connected/connecting"); + return false; + } + + if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "setAbsVolume: profile not connected"); + return false; + } + + stateMachine.sendMessage(VcpControllerStateMachine.SET_VOLUME, volume, audioType, device); + } + return true; + } + + /** + * Mute or unmute remote device via VCP connection + * + * @param device: remote device instance + * @param enableMute: true if mute, false if unmute + * @return true if mute requst is accepted, false if mute + * request is rejected + */ + public boolean setMute(BluetoothDevice device, boolean enableMute) { + synchronized (mStateMachines) { + Log.i(TAG, "setMute: device=" + device + ", " + "enableMute: " + enableMute); + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "setMute: device " + device + " was never connected/connecting"); + return false; + } + if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "setMute: profile not connected"); + return false; + } + if (enableMute) { + stateMachine.sendMessage(VcpControllerStateMachine.MUTE, device); + } else { + stateMachine.sendMessage(VcpControllerStateMachine.UNMUTE, device); + } + } + return true; + } + + /** + * Get current absolute volume of the remote device + * + * @param device: remote device instance + * @return current absolute volume of the remote device + */ + public int getAbsoluteVolume(BluetoothDevice device) { + synchronized (mStateMachines) { + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + return -1; + } + return stateMachine.getVolume(); + } + } + + /** + * Get mute status of remote device + * + * @param device: remote device instance + * @return current mute status of the remote device: + * true if mute status, false if unmute status + */ + public boolean isMute(BluetoothDevice device) { + synchronized (mStateMachines) { + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + return false; + } + return stateMachine.isMute(); + } + } + + /** + * Get the current connection state of the VCP + * + * @param device is the remote bluetooth device + * @return {@link BluetoothProfile#STATE_DISCONNECTED} if VCP is disconnected, + * {@link BluetoothProfile#STATE_CONNECTING} if VCP is being connected, + * {@link BluetoothProfile#STATE_CONNECTED} if VCP is connected, or + * {@link BluetoothProfile#STATE_DISCONNECTING} if VCP is being disconnected + */ + public int getConnectionState(BluetoothDevice device) { + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + /** + * Get current VCP Connection mode + * + * @param device: remote device instance + * @return current connection mode of VCP: + * {@link #BluetoothVcp.MODE_NONE} if none VCP connection + * {@link #BluetoothVcp.MODE_UNICAST} if VCP is connected for unicast + * {@link #BluetoothVcp.MODE_BROADCAST} if VCP is connected for broadcast + * {@link #BluetoothVcp.MODE_UNICAST_BROADCAST} if VCP is connected + * for both unicast and broadcast + */ + public int getConnectionMode(BluetoothDevice device) { + synchronized (mStateMachines) { + if (mConnectionMode.containsKey(device)) { + return mConnectionMode.get(device); + } + return BluetoothVcp.MODE_NONE; + } + } + + /** + * Check if VCP is connected for broadcast mode + * + * @param device: remote device instance + * @return true if VCP is connected for broadcast or uncast-broadcast + * return false if VCP is connected for unicast-only + */ + public boolean isBroadcastDevice(BluetoothDevice device) { + if (device == null) + return false; + + synchronized (mStateMachines) { + if (mConnectionMode.containsKey(device)) { + if ((mConnectionMode.get(device) & BluetoothVcp.MODE_BROADCAST) != 0) { + return true; + } + } + } + return false; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean okToConnect(BluetoothDevice device) { + // Check if this is an incoming connection in Quiet mode. + if (mAdapterService.isQuietModeEnabled()) { + Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); + return false; + } + + int bondState = mAdapterService.getBondState(device); + if (bondState != BluetoothDevice.BOND_BONDED) { + Log.w(TAG, "okToConnect: return false, bondState=" + bondState); + return false; + } + return true; + } + + void messageFromNative(VcpStackEvent stackEvent) { + Objects.requireNonNull(stackEvent.device, + "Device should never be null, event: " + stackEvent); + + synchronized (mStateMachines) { + BluetoothDevice device = stackEvent.device; + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + if (stackEvent.type == VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { + switch (stackEvent.valueInt1) { + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + sm = getOrCreateStateMachine(device); + break; + default: + break; + } + } + } + if (sm == null) { + Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); + return; + } + sm.sendMessage(VcpControllerStateMachine.STACK_EVENT, stackEvent); + } + } + + int getCsipSetId(BluetoothDevice device, ParcelUuid uuid) { + GroupService csipService = GroupService.getGroupService(); + if (csipService != null) { + return csipService.getRemoteDeviceGroupId(device, uuid); + } else { + return -1; + } + } + + void onConnectionStateChangedFromStateMachine(BluetoothDevice device, + int newState, int prevState) { + Log.d(TAG, "onConnectionStateChangedFromStateMachine for device: " + device + + " newState: " + newState); + + if (device == null) { + Log.d(TAG, "device is null "); + return; + } + + mDpm = DeviceProfileMap.getDeviceProfileMapInstance(); + mAcmService = AcmService.getAcmService(); + BluetoothDevice grpDevice; + if (mAcmService != null) { + grpDevice = mAcmService.getGroup(device); + } else { + Log.w(TAG, "AcmService is null"); + grpDevice = device; + } + + synchronized (mStateMachines) { + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + int bondState = mAdapterService.getBondState(device); + if (bondState == BluetoothDevice.BOND_NONE) { + removeStateMachine(device); + } + + if (mAcmService != null && + (mAcmService.isVcpPeerDeviceConnected(device, getCsipSetId(device, null)))) { + Log.d(TAG, "VCP Peer device connected, this is not last member, skip update to APM "); + } else { + ///* Update VCP profile disconnected to APM/ACM + Log.d(TAG, "All group members are disconnected, update to APM"); + if (mDpm != null) { + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, false); + + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.CALL_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, false); + } + } + setAbsVolumeSupport(device, false, -1); + updateConnState(device, newState); + //*/ + Log.d(TAG, "VCP get disconnected with renderer device: " + device); + } else if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "VCP get connected with renderer device: " + device); + + if (mAcmService != null && + (mAcmService.isVcpPeerDeviceConnected(device, getCsipSetId(device, null)))) { + Log.d(TAG, "VCP Peer device connected, this is not first connected member, skip update to APM "); + } else { + ///* Update VCP profile connected to APM/ACM + Log.d(TAG, "The first connected memeber, update to APM"); + if (mDpm != null) { + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, true); + + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.CALL_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, true); + } + } + // Set Abs Volume Support with true until get initial volume of remote + //*/ + } + } + } + + void setAbsVolumeSupport(BluetoothDevice device, boolean isAbsVolSupported, int initial_volume) { + mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + Log.d(TAG, "Update Abs Volume Support to upper layer "); + mAcmService.setAbsVolSupport(device, isAbsVolSupported, initial_volume); + } + } + + void notifyVolumeChanged(BluetoothDevice device, int volume, int audioType) { + Log.d(TAG, "notify volume changed for renderer device: " + device + " audioType: " + audioType); + // Notify ACM volume changed for device + mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + mAcmService.onVolumeStateChanged(device, volume, audioType); + } + Intent intent = new Intent(BluetoothVcp.ACTION_VOLUME_CHANGED); + intent.putExtra(BluetoothVcp.EXTRA_VOLUME, volume); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + void notifyMuteChanged(BluetoothDevice device, boolean mute) { + Log.d(TAG, "notify mute changed for renderer device: " + device + " mute: " + mute); + // Notify ACM mute changed + mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + mAcmService.onMuteStatusChanged (device, mute); + } + Intent intent = new Intent(BluetoothVcp.ACTION_MUTE_CHANGED); + intent.putExtra(BluetoothVcp.EXTRA_MUTE, mute); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + void broadcastConnectionModeChanged(BluetoothDevice device, int mode) { + Log.d(TAG, "broadccast connection mode changed for device: " + device + ", mode: " + mode); + Intent intent = new Intent(BluetoothVcp.ACTION_CONNECTION_MODE_CHANGED); + intent.putExtra(BluetoothVcp.EXTRA_MODE, mode); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + public void updateConnState(BluetoothDevice device, int newState) { + Log.d(TAG, "updateConnState: device: " + device + ", state: " + newState); + VolumeManager mVolumeManager = VolumeManager.get(); + mVolumeManager.onConnStateChange(device, newState, ApmConst.AudioProfiles.VCP); + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + return; + } + mConnectionMode.remove(device); + removeStateMachine(device); + } + } + + private void removeStateMachine(BluetoothDevice device) { + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.w(TAG, "removeStateMachine: device " + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.doQuit(); + sm.cleanup(); + mStateMachines.remove(device); + } + } + + private VcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + if (mStateMachines.size() >= MAX_VCP_STATE_MACHINES) { + Log.e(TAG, "Maximum number of VCP state machines reached: " + + MAX_VCP_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = VcpControllerStateMachine.make(device, this, mContext, + mNativeInterface, mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + + private class VcpControllerTestReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ACTION_CONNECT_DEVICE)) { + Log.d(TAG, "Receive ACTION_CONNECT_DEVICE"); + int mode = intent.getIntExtra(BluetoothVcp.EXTRA_MODE, 0); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + connect(device, mode); + } else if (action.equals(ACTION_DISCONNECT_DEVICE)) { + Log.d(TAG, "Receive ACTION_DISCONNECT_DEVICE"); + int mode = intent.getIntExtra(BluetoothVcp.EXTRA_MODE, 0); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + disconnect(device, mode); + } + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java new file mode 100644 index 0000000000000000000000000000000000000000..89cc69bf01d5f8b12a952524841e6571aace36ff --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java @@ -0,0 +1,200 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Defines the native interface that is used by state machine/service to + * send or receive messages from the native stack. This file is registered + * for the native methods in the corresponding JNI C++ file. + */ +package com.android.bluetooth.vcp; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Vcp Controller Native Interface to/from JNI. + */ +public class VcpControllerNativeInterface { + private static final String TAG = "VcpControllerNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + + @GuardedBy("INSTANCE_LOCK") + private static VcpControllerNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private VcpControllerNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtfStack(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static VcpControllerNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new VcpControllerNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + /** + * Initiates Vcp connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean connectVcp(BluetoothDevice device, boolean isDirect) { + return connectVcpNative(getByteAddress(device), isDirect); + } + + /** + * Disconnects Vcp from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectVcp(BluetoothDevice device) { + return disconnectVcpNative(getByteAddress(device)); + } + + /** + * Sets the Vcp Abs volume + * @param volume + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean setAbsVolume(int volume, BluetoothDevice device) { + return setAbsVolumeNative(volume, getByteAddress(device)); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mute(BluetoothDevice device) { + return muteNative(getByteAddress(device)); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean unmute(BluetoothDevice device) { + return unmuteNative(getByteAddress(device)); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + private void sendMessageToService(VcpStackEvent event) { + VcpController service = VcpController.getVcpController(); + if (service != null) { + service.messageFromNative(event); + } else { + Log.e(TAG, "Event ignored, service not available: " + event); + } + } + + // Callbacks from the native stack back into the Java framework. + // All callbacks are routed via the Service which will disambiguate which + // state machine the message should be routed to. + private void onConnectionStateChanged(int state, byte[] address) { + VcpStackEvent event = + new VcpStackEvent(VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + + if (DBG) { + Log.d(TAG, "onConnectionStateChanged: " + event); + } + sendMessageToService(event); + } + + private void OnVolumeStateChange(int volume, int mute, byte[] address) { + VcpStackEvent event = new VcpStackEvent( + VcpStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = volume; + event.valueInt2 = mute; + + if (DBG) { + Log.d(TAG, "OnVolumeStateChange: " + event); + } + sendMessageToService(event); + } + + private void OnVolumeFlagsChange(int flags, byte[] address) { + VcpStackEvent event = new VcpStackEvent( + VcpStackEvent.EVENT_TYPE_VOLUME_FLAGS_CHANGED); + event.device = getDevice(address); + event.valueInt1 = flags; + + if (DBG) { + Log.d(TAG, "OnVolumeFlagsChange: " + event); + } + sendMessageToService(event); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean connectVcpNative(byte[] address, boolean isDirect); + private native boolean disconnectVcpNative(byte[] address); + private native boolean setAbsVolumeNative(int volume, byte[] address); + private native boolean muteNative(byte[] address); + private native boolean unmuteNative(byte[] address); +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java new file mode 100644 index 0000000000000000000000000000000000000000..fc35753ddc46668412af77cbfdb26a237adac6c4 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java @@ -0,0 +1,1046 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Bluetooth VCP StateMachine. There is one instance per remote device. + * - "Disconnected" and "Connected" are steady states. + * - "Connecting" and "Disconnecting" are transient states until the + * connection / disconnection is completed. + * + * + * (Disconnected) + * | ^ + * CONNECT | | DISCONNECTED + * V | + * (Connecting)<--->(Disconnecting) + * | ^ + * CONNECTED | | DISCONNECT + * V | + * (Connected) + * NOTES: + * - If state machine is in "Connecting" state and the remote device sends + * DISCONNECT request, the state machine transitions to "Disconnecting" state. + * - Similarly, if the state machine is in "Disconnecting" state and the remote device + * sends CONNECT request, the state machine transitions to "Connecting" state. + * + * DISCONNECT + * (Connecting) ---------------> (Disconnecting) + * <--------------- + * CONNECT + * + */ + +package com.android.bluetooth.vcp; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothVcp; +import android.content.Context; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; + +final class VcpControllerStateMachine extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "VcpControllerStateMachine"; + + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int SET_VOLUME = 3; + static final int MUTE = 4; + static final int UNMUTE = 5; + + @VisibleForTesting + static final int STACK_EVENT = 101; + private static final int CONNECT_TIMEOUT = 201; + private static final int SET_ABS_VOL_TIMEOUT = 202; + private static final int CHANGE_MUTE_TIMEOUT = 203; + + private static final int UNMUTE_STATE = 0; + private static final int MUTE_STATE = 1; + private static final int VOLUME_SETTING_NOT_PERSISTED = 0x00; + private static final int VOLUME_SETTING_PERSISTED = 0x01; + + private static final int MAX_ERROR_RETRY_TIMES = 3; + private static final int VCP_MAX_VOL = 255; + // The default VCP volume 0x77 (119) + private static final int VCP_DEFAULT_VOL = 119; + private static final int CMD_TIMEOUT_DELAY = 2000; + + + // NOTE: the value is not "final" - it is modified in the unit tests + @VisibleForTesting + static int sConnectTimeoutMs = 30000; // 30s + + private Disconnected mDisconnected; + private Connecting mConnecting; + private Disconnecting mDisconnecting; + private Connected mConnected; + private int mLastConnectionState = -1; + + private VcpController mVcpController; + private VcpControllerNativeInterface mNativeInterface; + private Context mContext; + + /* Current remote volume */ + private int mRemoteVolume; + /* Requested volume in progress of Native Layer setAbsVolume */ + private int mRequestedVolume; + /* Cached new volume if has a requested volume in progress */ + private int mCachedVolume; + private int mAbsVolRetryTimes; + private boolean mAbsVolSetInProgress; + + /* Current remote mute state */ + private int mMuteState; + /* Requested mute state in progress of Native Layer mute/unMute */ + private int mRequestedMuteState; + /* Cached new mute state if has a requested mute state in progress */ + private int mCachedMuteState; + private int mChangeMuteRetryTimes; + private boolean mMuteChangeInProgress; + + private int mVolumeFlags; + private final BluetoothDevice mDevice; + private int mVolumeControlAudioType; + private int mCachedVolumeControlAudioType; + + VcpControllerStateMachine(BluetoothDevice device, VcpController svc, Context context, + VcpControllerNativeInterface nativeInterface, Looper looper) { + super(TAG, looper); + mDevice = device; + mVcpController = svc; + mContext = context; + mNativeInterface = nativeInterface; + + mDisconnected = new Disconnected(); + mConnecting = new Connecting(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + + addState(mDisconnected); + addState(mConnecting); + addState(mDisconnecting); + addState(mConnected); + + setInitialState(mDisconnected); + } + + static VcpControllerStateMachine make(BluetoothDevice device, VcpController svc, + Context context, VcpControllerNativeInterface nativeInterface, Looper looper) { + Log.i(TAG, "make for device " + device); + VcpControllerStateMachine VcpControllerSm = + new VcpControllerStateMachine(device, svc, context, nativeInterface, looper); + VcpControllerSm.start(); + return VcpControllerSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + } + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + + removeDeferredMessages(DISCONNECT); + if (mLastConnectionState != -1) { + // Don't broadcast during startup + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED, + mLastConnectionState); + } + + cleanupDevice(); + } + + @Override + public void exit() { + log("Exit Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + BluetoothDevice device = (BluetoothDevice) message.obj; + log("Connecting to " + device); + + if (!mDevice.equals(device)) { + Log.e(TAG, "CONNECT failed, device=" + device + ", currentDev=" + mDevice); + break; + } + + if (!mNativeInterface.connectVcp(mDevice, true)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + + transitionTo(mConnecting); + break; + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + if (DBG) { + Log.d(TAG, "Disconnected: stack event: " + event); + } + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + break; + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + Log.e(TAG, "Unexpected msg " + messageWhatToString(message.what) + + ": " + message); + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnected state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Ignore VCP DISCONNECTED event: " + mDevice); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + Log.i(TAG, "Incoming VCP Connecting request accepted: " + mDevice); + if (mVcpController.okToConnect(mDevice)) { + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming VCP Connecting request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + Log.w(TAG, "VCP Connected from Disconnected state: " + mDevice); + if (mVcpController.okToConnect(mDevice)) { + Log.i(TAG, "Incoming VCP Connected request accepted: " + mDevice); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming VCP Connected request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Ignore VCP DISCONNECTING event: " + mDevice); + break; + default: + Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice); + break; + } + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + log("Exit Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "Connecting connection timeout: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + // We timed out trying to connect, transition to Disconnected state + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + Log.w(TAG, "CONNECT_TIMEOUT"); + transitionTo(mDisconnected); + break; + case DISCONNECT: + log("Connecting: connection canceled to " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + transitionTo(mDisconnected); + break; + case SET_VOLUME: + case MUTE: + case UNMUTE: + case SET_ABS_VOL_TIMEOUT: + case CHANGE_MUTE_TIMEOUT: + deferMessage(message); + break; + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + log("Connecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Connecting: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connecting state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Connecting device disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + transitionTo(mConnected); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + log("Exit Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Disconnecting connection timeout: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + // We timed out trying to connect, transition to Disconnected state + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + transitionTo(mDisconnected); + Log.w(TAG, "CONNECT_TIMEOUT"); + break; + } + case DISCONNECT: + deferMessage(message); + break; + case SET_VOLUME: + case MUTE: + case UNMUTE: + case SET_ABS_VOL_TIMEOUT: + case CHANGE_MUTE_TIMEOUT: + deferMessage(message); + break; + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + log("Disconnecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Disconnecting: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnecting state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + if (mVcpController.okToConnect(mDevice)) { + Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming VCP Connected request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + if (mVcpController.okToConnect(mDevice)) { + Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice); + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming VCP Connecting request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + removeDeferredMessages(CONNECT); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState); + } + + @Override + public void exit() { + log("Exit Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); + break; + } + case DISCONNECT: { + log("Disconnecting from " + mDevice); + if (!mNativeInterface.disconnectVcp(mDevice)) { + // If error in the native stack, transition directly to Disconnected state. + Log.e(TAG, "Connected: error disconnecting from " + mDevice); + transitionTo(mDisconnected); + break; + } + transitionTo(mDisconnecting); + break; + } + case SET_VOLUME: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "SET_VOLUME failed " + device + + " is not currentDevice"); + break; + } + log("Set volume for " + device); + + processSetAbsVolume(message.arg1, message.arg2); + break; + } + case MUTE: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Mute failed " + device + + " is not currentDevice"); + break; + } + log("Mute for " + device); + + processSetMute(); + break; + } + case UNMUTE: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Unmute failed " + device + + " is not currentDevice"); + break; + } + log("Unmute for " + device); + + processSetUnmute(); + break; + } + case SET_ABS_VOL_TIMEOUT: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Set abs vol timeout failed " + device + + " is not currentDevice"); + break; + } + + mAbsVolSetInProgress = false; + if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { + Log.w(TAG, "Set abs vol retry exceed max times"); + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + break; + } else { + mAbsVolRetryTimes += 1; + if (mNativeInterface.setAbsVolume(mRequestedVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + } else { + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + } + } + break; + } + case CHANGE_MUTE_TIMEOUT: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Mute timeout failed " + device + + " is not currentDevice"); + break; + } + + mMuteChangeInProgress = false; + if (mChangeMuteRetryTimes >= MAX_ERROR_RETRY_TIMES) { + Log.w(TAG, "Mute retry exceed max times"); + mChangeMuteRetryTimes = 0; + mRequestedMuteState = -1; + break; + } else { + mChangeMuteRetryTimes += 1; + boolean ret; + if (mRequestedMuteState == MUTE_STATE) { + ret = mNativeInterface.mute(mDevice); + } else { + ret = mNativeInterface.unmute(mDevice); + } + + if (ret) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + } else { + mChangeMuteRetryTimes = 0; + mRequestedMuteState = -1; + Log.e(TAG, "Change Mute failed for device: " + mDevice); + } + } + break; + } + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + log("Connected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case VcpStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED: + processVolumeStateEvent(event.valueInt1, event.valueInt2); + break; + case VcpStackEvent.EVENT_TYPE_VOLUME_FLAGS_CHANGED: + processVolumeFlagsChanged(event.valueInt1); + break; + default: + Log.e(TAG, "Connected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connected state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected from " + mDevice); + transitionTo(mDisconnected); + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.i(TAG, "Disconnecting from " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state); + break; + } + } + } + + private void processSetAbsVolume(int volume, int audioType) { + log("process set absolute volume"); + + if (mAbsVolSetInProgress) { + mCachedVolume = volume; + mCachedVolumeControlAudioType = audioType; + Log.w(TAG, "There is already a volume command in progress, cache volume: " + + mCachedVolume + " cached audio type: " + audioType); + return; + } + + if (mRemoteVolume == -1) { + Log.w(TAG, "remote not tell initial volume"); + return; + } + + if (mRemoteVolume == volume) { + Log.w(TAG, "Ignore set abs volume as current volume equals to requested volume"); + return; + } + + Log.d(TAG, "set abs volume for audio type: " + audioType); + if (mNativeInterface.setAbsVolume(volume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + mRequestedVolume = volume; + mVolumeControlAudioType = audioType; + mCachedVolume = -1; + } else { + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + } + } + + private void processSetMute() { + log("process mute"); + + if (mMuteChangeInProgress) { + mCachedMuteState = MUTE_STATE; + Log.w(TAG, "There is already a mute change in progress, cache mute"); + return; + } + + if (mRemoteVolume == -1) { + Log.w(TAG, "remote not tell initial volume"); + return; + } + + if (mMuteState == MUTE_STATE) { + Log.w(TAG, "Ignore mute request as current state is mute"); + return; + } + + if (mNativeInterface.mute(mDevice)) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + mRequestedMuteState = MUTE_STATE; + mCachedMuteState = -1; + } else { + Log.e(TAG, "Mute failed for device: " + mDevice); + } + } + + private void processSetUnmute() { + log("process unmute"); + + if (mMuteChangeInProgress) { + mCachedMuteState = UNMUTE_STATE; + Log.w(TAG, "There is already a mute change in progress, cache unmute"); + return; + } + + if (mRemoteVolume == -1) { + Log.w(TAG, "remote not tell initial volume"); + return; + } + + if (mMuteState == UNMUTE_STATE) { + Log.w(TAG, "Ignore unmute request as current state is unmute"); + return; + } + + if (mNativeInterface.unmute(mDevice)) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + mRequestedMuteState = UNMUTE_STATE; + mCachedMuteState = -1; + } else { + Log.e(TAG, "Unmute failed for device: " + mDevice); + } + } + + private void processVolumeStateEvent(int vcpVol, int mute) { + log("process volume state event"); + + if (mRemoteVolume == -1 || mMuteState != mute || + mMuteChangeInProgress == true) { + processMuteChanged(mute); + } + + if (mRemoteVolume == -1 || mRemoteVolume != vcpVol || + mAbsVolSetInProgress == true) { + processVolumeChanged(vcpVol); + } + } + + private void processVolumeChanged(int vcpVol) { + log("process volume setting changed"); + + if (mAbsVolSetInProgress == true) { + mAbsVolSetInProgress = false; + removeMessages(SET_ABS_VOL_TIMEOUT); + if (mRequestedVolume == vcpVol) { + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + + if ((mCachedVolume != -1) && (mCachedVolume != vcpVol)) { + mVcpController.notifyVolumeChanged(mDevice, vcpVol, mVolumeControlAudioType); + mVolumeControlAudioType = -1; + Log.w(TAG, "Set cached volume to remote"); + if (mNativeInterface.setAbsVolume(mCachedVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + mRequestedVolume = mCachedVolume; + mVolumeControlAudioType = mCachedVolumeControlAudioType; + mCachedVolumeControlAudioType = -1; + mCachedVolume = -1; + return; + } else { + Log.e(TAG, "Set cached volume failed for device: " + mDevice); + mCachedVolume = -1; + } + } + } else { + Log.w(TAG, "Remote changed volume not equal to requested volume"); + if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { + Log.w(TAG, "Set abs vol retry exceed max times"); + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + } else { + mAbsVolRetryTimes += 1; + if (mNativeInterface.setAbsVolume(mRequestedVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + return; + } else { + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + } + } + } + } + + if (mRemoteVolume == -1) { + // Set initial volume if volume flags is not persisted + if ((mVolumeFlags == VOLUME_SETTING_NOT_PERSISTED)) { + int initialVolume = VCP_DEFAULT_VOL; + if (vcpVol != initialVolume) { + mRemoteVolume = vcpVol; + Log.w(TAG, "Set initial volume to remote if volume persisted flag is false"); + if (mNativeInterface.setAbsVolume(initialVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + mRequestedVolume = initialVolume; + mVcpController.setAbsVolumeSupport(mDevice, true, initialVolume); + mVcpController.updateConnState(mDevice, BluetoothProfile.STATE_CONNECTED); + return; + } else { + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + } + } + } + Log.w(TAG, "Set abs volume support and update initial volume to ACM"); + mRemoteVolume = vcpVol; + mVcpController.setAbsVolumeSupport(mDevice, true, vcpVol); + mVcpController.updateConnState(mDevice, BluetoothProfile.STATE_CONNECTED); + return; + } + + if (mRemoteVolume != vcpVol) { + mRemoteVolume = vcpVol; + mVcpController.notifyVolumeChanged(mDevice, vcpVol, mVolumeControlAudioType); + mVolumeControlAudioType = -1; + long pecentVolChanged = ((long)vcpVol * 100) / 0xff; + Log.w(TAG, "percent volume changed: " + pecentVolChanged + "%"); + } + } + + private void processMuteChanged(int mute) { + log("process mute changed"); + + if (mMuteChangeInProgress == true) { + mMuteChangeInProgress = false; + mChangeMuteRetryTimes = 0; + removeMessages(CHANGE_MUTE_TIMEOUT); + + if ((mCachedMuteState != -1) && (mCachedMuteState != mute)) { + Log.w(TAG, "Set cached mute state to remote"); + boolean ret; + if (mCachedMuteState == MUTE_STATE) { + ret = mNativeInterface.mute(mDevice); + } else { + ret = mNativeInterface.unmute(mDevice); + } + + if (ret) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + mRequestedMuteState = mCachedMuteState; + mCachedMuteState = -1; + return; + } + mCachedMuteState = -1; + } + } + + if (mMuteState != mute) { + mMuteState = mute; + boolean isMute = (mMuteState == MUTE_STATE) ? true : false; + mVcpController.notifyMuteChanged(mDevice, isMute); + Log.w(TAG, "Mute state changed to " + mMuteState); + } + } + + private void processVolumeFlagsChanged(int flags) { + log("process volume flags changed"); + mVolumeFlags = flags; + } + + int getConnectionState() { + String currentState = getCurrentState().getName(); + switch (currentState) { + case "Disconnected": + return BluetoothProfile.STATE_DISCONNECTED; + case "Connecting": + return BluetoothProfile.STATE_CONNECTING; + case "Connected": + return BluetoothProfile.STATE_CONNECTED; + case "Disconnecting": + return BluetoothProfile.STATE_DISCONNECTING; + default: + Log.e(TAG, "Bad currentState: " + currentState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + int getVolume() { + return mRemoteVolume; + } + + boolean isMute() { + if (mMuteState == MUTE_STATE) + return true; + else + return false; + } + + BluetoothDevice getDevice() { + return mDevice; + } + + synchronized boolean isConnected() { + return getCurrentState() == mConnected; + } + + // This method does not check for error condition (newState == prevState) + private void broadcastConnectionState(int newState, int prevState) { + log("Connection state " + mDevice + ": " + profileStateToString(prevState) + + "->" + profileStateToString(newState)); + mVcpController.onConnectionStateChangedFromStateMachine(mDevice, + newState, prevState); + + Intent intent = new Intent(BluetoothVcp.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + private void cleanupDevice() { + log("cleanup device " + mDevice); + mRemoteVolume = -1; + mRequestedVolume = -1; + mCachedVolume = -1; + mAbsVolRetryTimes = 0; + mAbsVolSetInProgress = false; + mMuteState = -1; + mRequestedMuteState = -1; + mCachedMuteState = -1; + mChangeMuteRetryTimes = 0; + mMuteChangeInProgress = false; + mVolumeFlags = -1; + mVolumeControlAudioType = -1; + mCachedVolumeControlAudioType = -1; + } + + private static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case STACK_EVENT: + return "STACK_EVENT"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + case SET_VOLUME: + return "SET_VOLUME"; + case MUTE: + return "MUTE"; + case UNMUTE: + return "UNMUTE"; + case SET_ABS_VOL_TIMEOUT: + return "SET_ABS_VOL_TIMEOUT"; + case CHANGE_MUTE_TIMEOUT: + return "CHANGE_MUTE_TIMEOUT"; + default: + return "UNKNOWN(" + what + ")"; + } + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mDevice: " + mDevice); + ProfileService.println(sb, " StateMachine: " + this); + // Dump the state machine logs + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + super.dump(new FileDescriptor(), printWriter, new String[]{}); + printWriter.flush(); + stringWriter.flush(); + ProfileService.println(sb, " StateMachineLog:"); + Scanner scanner = new Scanner(stringWriter.toString()); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + ProfileService.println(sb, " " + line); + } + scanner.close(); + } + + @Override + protected void log(String msg) { + if (DBG) { + super.log(msg); + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..e77f95d84831f2aa7b795a3ff63e067146a51d2e --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java @@ -0,0 +1,79 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.vcp; + +import android.bluetooth.BluetoothDevice; + +/** + * Stack event sent via a callback from JNI to Java, or generated + * internally by the VCP State Machine. + */ +public class VcpStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + public static final int EVENT_TYPE_VOLUME_STATE_CHANGED = 2; + public static final int EVENT_TYPE_VOLUME_FLAGS_CHANGED = 3; + + // Do not modify without updating the HAL bt_vcp.h files. + // Match up with enum class ConnectionState of bt_vcp_controller.h. + static final int CONNECTION_STATE_DISCONNECTED = 0; + static final int CONNECTION_STATE_CONNECTING = 1; + static final int CONNECTION_STATE_CONNECTED = 2; + static final int CONNECTION_STATE_DISCONNECTING = 3; + + public int type; + public BluetoothDevice device; + public int valueInt1; + public int valueInt2; + + VcpStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("VcpStackEvent {type:" + eventTypeToString(type)); + result.append(", device:" + device); + result.append(", value1:" + valueInt1); + result.append(", value2:" + valueInt2); + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + return "EVENT_TYPE_CONNECTION_STATE_CHANGED"; + case EVENT_TYPE_VOLUME_STATE_CHANGED: + return "EVENT_TYPE_VOLUME_STATE_CHANGED"; + case EVENT_TYPE_VOLUME_FLAGS_CHANGED: + return "EVENT_TYPE_VOLUME_FLAGS_CHANGED"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } +} + diff --git a/le_audio/packages/apps/Settings/Android.bp b/le_audio/packages/apps/Settings/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..ec3867dc40f02651cfc3725c66cb79855a6b992b --- /dev/null +++ b/le_audio/packages/apps/Settings/Android.bp @@ -0,0 +1,22 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +filegroup { + name: "settings-bluetooth-adva-srcs", + srcs: ["src/**/*.java"], +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java new file mode 100644 index 0000000000000000000000000000000000000000..7f79465ded5ad9fd826bfe997a9d24ce751afff7 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java @@ -0,0 +1,144 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.bluetooth; + +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settings.bluetooth.BluetoothDeviceUpdater; +import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater; +import com.android.settings.connecteddevice.dock.DockUpdater; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import android.util.Log; +import androidx.annotation.Keep; + +@Keep +public class BADevicePreferenceController extends BasePreferenceController + implements LifecycleObserver, OnStart, OnStop, BleBroadcastSourceInfoPreferenceCallback { + + private static final String TAG = "BADevicePreferenceController"; + //Up to 3 Elements can be viewed here + private static final int MAX_DEVICE_NUM = 3; + + private PreferenceGroup mPreferenceGroup; + private BluetoothBroadcastSourceInfoEntries mBleSourceInfoUpdater; + private int mPreferenceSize; + private CachedBluetoothDevice mCachedDevice; + + public BADevicePreferenceController(Context context, Lifecycle lifecycle, String preferenceKey) { + super(context, preferenceKey); + + lifecycle.addObserver(this); + BroadcastScanAssistanceUtils.debug(TAG, "constructor: KEY" + preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) + ) + ? AVAILABLE + : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public String getPreferenceKey() { + return new String("added_sources"); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + BroadcastScanAssistanceUtils.debug(TAG, "displayPreference"); + super.displayPreference(screen); + mPreferenceGroup = screen.findPreference(getPreferenceKey()); + mPreferenceGroup.setVisible(false); + + if (isAvailable()) { + BroadcastScanAssistanceUtils.debug(TAG, "registering wth BleSrcInfo updaters"); + final Context context = screen.getContext(); + if (mBleSourceInfoUpdater != null) { + mBleSourceInfoUpdater.setPrefContext(context); + } + } + } + + @Override + public void onStart() { + if (mBleSourceInfoUpdater != null) { + mBleSourceInfoUpdater.registerCallback(); + } + } + + @Override + public void onStop() { + if (mBleSourceInfoUpdater != null) { + mBleSourceInfoUpdater.unregisterCallback(); + } + } + + public void init(DashboardFragment fragment, CachedBluetoothDevice device) { + BroadcastScanAssistanceUtils.debug(TAG, "Init"); + mCachedDevice = device; + mBleSourceInfoUpdater = new BluetoothBroadcastSourceInfoEntries(fragment.getContext(), + fragment, BADevicePreferenceController.this, + device); + mPreferenceSize = 0; + } + + @Override + public void onBroadcastSourceInfoAdded(Preference preference) { + BroadcastScanAssistanceUtils.debug(TAG, "onBroadcastSourceInfoAdded"); + + if (mPreferenceSize < MAX_DEVICE_NUM) { + boolean ret = mPreferenceGroup.addPreference(preference); + BroadcastScanAssistanceUtils.debug(TAG, "addPreference returns" + ret); + mPreferenceSize++; + } + updatePreferenceVisiblity(); + } + + @Override + public void onBroadcastSourceInfoRemoved(Preference preference) { + BroadcastScanAssistanceUtils.debug(TAG, "onBroadcastSourceInfoRemoved"); + mPreferenceSize--; + boolean ret = mPreferenceGroup.removePreference(preference); + BroadcastScanAssistanceUtils.debug(TAG, "removePreference returns " + ret); + updatePreferenceVisiblity(); + } + + @VisibleForTesting + void setPreferenceGroup(PreferenceGroup preferenceGroup) { + mPreferenceGroup = preferenceGroup; + } + + @VisibleForTesting + void updatePreferenceVisiblity() { + BroadcastScanAssistanceUtils.debug(TAG, "updatePreferenceVisiblity:" + mPreferenceSize); + mPreferenceGroup.setVisible(mPreferenceSize > 0); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java new file mode 100644 index 0000000000000000000000000000000000000000..e470b29f0843a358654a212155e9578e46f5103e --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastSourceChannel; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import java.lang.String; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.preference.EditTextPreference; +import androidx.preference.MultiSelectListPreference; +import com.android.settingslib.widget.ActionButtonsPreference; + +import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.VendorCachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.MapProfile; +import com.android.settingslib.bluetooth.PanProfile; +import com.android.settingslib.bluetooth.PbapServerProfile; +import com.android.settingslib.core.lifecycle.Lifecycle; +import androidx.appcompat.app.AlertDialog; +import android.text.Html; +import android.text.TextUtils; +import android.content.DialogInterface; +import android.widget.Toast; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; +import java.util.Iterator; +import com.android.settings.R; +import java.util.Map; + +/** + * This class Broadcast Source Info details of the given Scan delegator + */ +public class BleBroadcastSourceInfoDetailsController extends BluetoothDetailsController + implements Preference.OnPreferenceClickListener, + Preference.OnPreferenceChangeListener, CachedBluetoothDevice.Callback { + private static final String TAG = "BleBroadcastSourceInfoDetailsController"; + private final String EMPTY_BD_ADDRESS = "00:00:00:00:00:00"; + + //Display controls + private static final String KEY_SOURCE_INFO_GROUP = "broadcast_source_details_category"; + private static final String KEY_SOURCE_ID = "broadcast_si_sourceId"; + private static final String KEY_SOURCE_DEVICE = "broadcast_si_source_address"; + private static final String KEY_SOURCE_ENC_STATUS = "broadcast_si_encryption_state"; + private static final String KEY_SOURCE_METADATA = "broadcast_si_metadata"; + private static final String KEY_SOURCE_METADATA_STATE = "broadcast_si_metadata_state"; + private static final String KEY_SOURCE_AUDIO_STATE = "broadcast_si_audio_state"; + + //Input Controls + private static final String KEY_SOURCE_METADATA_SWITCH = "broadcast_si_enable_metadata_sync"; + private static final String KEY_SOURCE_AUDIOSYNC_SWITCH = "broadcast_si_enable_audio_sync"; + private static final String KEY_UPDATE_BCAST_CODE = "update_broadcast_code"; + private static final String KEY_UPDATE_SOURCE_INFO = "bcast_si_update_button"; + private static final String KEY_REMOVE_SOURCE_INFO = "bcast_si_remove_button"; + + private CachedBluetoothDevice mCachedDevice; + private VendorCachedBluetoothDevice mVendorCachedDevice; + private PreferenceCategory mSourceInfoContainer; + + private Preference mSourceIdPref; + private Preference mSourceDevicePref; + private Preference mSourceEncStatusPref; + private Preference mSourceMetadataPref; + private Preference mSourceMetadataSyncStatusPref; + private MultiSelectListPreference mSourceAudioSyncStatusPref; + private SwitchPreference mSourceMetadataSyncSwitchPref; + private SwitchPreference mSourceAudioSyncSwitchPref; + private EditTextPreference mSourceUpdateBcastCodePref; + private ActionButtonsPreference mSourceUpdateSourceInfoPref; + private ActionButtonsPreference mSourceRemoveSourceInfoPref; + private boolean mIsValueChanged = false; + private BleBroadcastSourceInfo mBleBroadcastSourceInfo; + private BleBroadcastAudioScanAssistManager mScanAssistanceMgr; + private boolean isBroadcastPINUpdated = false; + private String mBroadcastCode; + private int mSourceInfoIndex; + private String EMPTY_ENTRY = "EMPTY ENTRY"; + private int mMetadataSyncState; + private int mAudioSyncState; + private boolean mIsButtonRefreshOnly = false; + private boolean mGroupOp = false; + private AlertDialog mScanAssistGroupOpDialog = null; + private List mBisIndicies; + private boolean mPAsyncCtrlNeeded = false; + + public BleBroadcastSourceInfoDetailsController(Context context, + PreferenceFragmentCompat fragment, + BleBroadcastSourceInfo bleSourceInfo, CachedBluetoothDevice device, + int sourceInfoIndex, Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + Context mContext = context; + mBleBroadcastSourceInfo = bleSourceInfo; + mCachedDevice = device; + LocalBluetoothManager mgr = Utils.getLocalBtManager(context); + LocalBluetoothProfileManager profileManager = mgr.getProfileManager(); + mVendorCachedDevice = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(device, profileManager); + mScanAssistanceMgr = mVendorCachedDevice.getScanAssistManager(); + lifecycle.addObserver(this); + mSourceInfoIndex = sourceInfoIndex; + clearInputs(); + mPAsyncCtrlNeeded = false; + } + + private void clearInputs() + { //Keep the default state of Metadata as ON always + mMetadataSyncState = + BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC; + mAudioSyncState = + BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID; + mBroadcastCode = null; + isBroadcastPINUpdated = false; + } + + private void triggerRemoveBroadcastSource() { + if (mScanAssistanceMgr != null) { + mScanAssistanceMgr.removeBroadcastSource( + mBleBroadcastSourceInfo.getSourceId(), mGroupOp); + } + } + + private void onRemoveBroadcastSourceInfoPressed() { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onRemoveBroadcastSourceInfoPressed:" + + mBleBroadcastSourceInfo); + + ///*_CSIP + if (mCachedDevice.isGroupDevice()) { + String name = mCachedDevice.getName(); + if (TextUtils.isEmpty(name)) { + name = mContext.getString(R.string.bluetooth_device); + } + String message = mContext.getString(R.string.group_remove_source_message, name); + String title = mContext.getString(R.string.group_remove_source_title); + + DialogInterface.OnClickListener groupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mScanAssistGroupOpDialog != null) { + mScanAssistGroupOpDialog.dismiss(); + } + mGroupOp = true; + triggerRemoveBroadcastSource(); + } + }; + DialogInterface.OnClickListener nonGroupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mScanAssistGroupOpDialog != null) { + mScanAssistGroupOpDialog.dismiss(); + } + + mGroupOp = false; + triggerRemoveBroadcastSource(); + } + }; + mGroupOp = false; + mScanAssistGroupOpDialog = BroadcastScanAssistanceUtils.showAssistanceGroupOptionsDialog(mContext, + mScanAssistGroupOpDialog, groupOpListener, nonGroupOpListener, title, Html.fromHtml(message)); + } else { + //_CSIP*/ + mGroupOp = false; + triggerRemoveBroadcastSource(); + ///*_CSIP + } + //_CSIP*/ + } + + private int getSyncState(int metadataSyncState, int audioSyncState) { + + if (audioSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED && + metadataSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + return BleBroadcastAudioScanAssistManager.SYNC_METADATA_AUDIO; + } + + if (audioSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED && + metadataSyncState != BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + return BleBroadcastAudioScanAssistManager.SYNC_AUDIO; + } + if (audioSyncState != BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED && + metadataSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + return BleBroadcastAudioScanAssistManager.SYNC_METADATA; + } + + return -1; + } + + private void triggerUpdateBroadcastSource() { + if (mScanAssistanceMgr != null) { + if (mIsValueChanged == true) { + int syncState = getSyncState(mMetadataSyncState, mAudioSyncState); + if (syncState == -1) { + Log.e(TAG, "triggerUpdateBroadcastSource: Invalid sync Input, Ignore"); + return; + } + mScanAssistanceMgr.updateBroadcastSource( + mBleBroadcastSourceInfo.getSourceId(), + getSyncState(mMetadataSyncState, mAudioSyncState), + mBisIndicies, mGroupOp); + mIsValueChanged = false; + } + if (isBroadcastPINUpdated) { + mScanAssistanceMgr.setBroadcastCode( + mBleBroadcastSourceInfo.getSourceId(),mBroadcastCode, mGroupOp); + isBroadcastPINUpdated = false; + } + clearInputs(); + } + } + + private void onUpdateBroadcastSourceInfoPressed() { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + + "onUpdateBroadcastSourceInfoPressed:" + mBleBroadcastSourceInfo); + + ///*_CSIP + if (mCachedDevice.isGroupDevice()) { + String name = mCachedDevice.getName(); + if (TextUtils.isEmpty(name)) { + name = mContext.getString(R.string.bluetooth_device); + } + String message = mContext.getString(R.string.group_update_source_message, name); + String title = mContext.getString(R.string.group_update_source_title); + + DialogInterface.OnClickListener groupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mGroupOp = true; + triggerUpdateBroadcastSource(); + } + }; + DialogInterface.OnClickListener nonGroupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mGroupOp = false; + triggerUpdateBroadcastSource(); + } + }; + mGroupOp = false; + mScanAssistGroupOpDialog = BroadcastScanAssistanceUtils.showAssistanceGroupOptionsDialog(mContext, + mScanAssistGroupOpDialog, groupOpListener, nonGroupOpListener, title, Html.fromHtml(message)); + } else { + //_CSIP*/ + mGroupOp = false; + triggerUpdateBroadcastSource(); + ///*_CSIP + } + //_CSIP*/ + } + + @Override + protected void init(PreferenceScreen screen) { + mSourceInfoContainer = + (PreferenceCategory)screen.findPreference(getPreferenceKey()); + mSourceIdPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_ID); + mSourceDevicePref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_DEVICE); + mSourceEncStatusPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_ENC_STATUS); + mSourceMetadataPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_METADATA); + mSourceMetadataSyncStatusPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_METADATA_STATE); + if (mPAsyncCtrlNeeded) { + mSourceMetadataSyncSwitchPref = (SwitchPreference)mSourceInfoContainer.findPreference( + KEY_SOURCE_METADATA_SWITCH); + + if (mSourceMetadataSyncSwitchPref != null) { + mSourceMetadataSyncSwitchPref.setOnPreferenceClickListener(this); + } + } + mSourceAudioSyncStatusPref = (MultiSelectListPreference)mSourceInfoContainer.findPreference( + KEY_SOURCE_AUDIO_STATE); + if (mSourceAudioSyncStatusPref != null) { + mSourceAudioSyncStatusPref.setOnPreferenceChangeListener(this); + } + + mSourceAudioSyncSwitchPref = (SwitchPreference)mSourceInfoContainer.findPreference( + KEY_SOURCE_AUDIOSYNC_SWITCH); + if (mSourceAudioSyncSwitchPref != null) { + mSourceAudioSyncSwitchPref.setOnPreferenceClickListener(this); + } + mSourceUpdateBcastCodePref = + (EditTextPreference)mSourceInfoContainer.findPreference( + KEY_UPDATE_BCAST_CODE); + if (mSourceUpdateBcastCodePref != null) { + mSourceUpdateBcastCodePref.setOnPreferenceClickListener(this); + mSourceUpdateBcastCodePref.setOnPreferenceChangeListener(this); + } + mSourceUpdateSourceInfoPref = + ((ActionButtonsPreference)mSourceInfoContainer.findPreference( + KEY_UPDATE_SOURCE_INFO)) + .setButton1Text(R.string.update_sourceinfo_btn_txt) + .setButton1Enabled(false) + .setButton1OnClickListener((view)->onUpdateBroadcastSourceInfoPressed()) + .setButton2Text(R.string.remove_sourceinfo_btn_txt) + .setButton2Icon(R.drawable.ic_settings_close) + .setButton2Enabled(false) + .setButton2OnClickListener((view)->onRemoveBroadcastSourceInfoPressed()); + refresh(); + } + + @Override + public void onDeviceAttributesChanged() { + //update the Local variable If the receiverState is + //updated with some values + final Map srcInfos = + mVendorCachedDevice.getAllBleBroadcastreceiverStates(); + if (srcInfos == null) { + return; + } + for (Map.Entry entry: srcInfos.entrySet()) { + Integer index = entry.getKey(); + BleBroadcastSourceInfo sourceInfo = entry.getValue(); + String toastString = null; + if (index == mSourceInfoIndex) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":matching source Info"); + if (sourceInfo.isEmptyEntry()) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":source info seem to be removed"); + toastString = "Source Info Removal"; + mBleBroadcastSourceInfo = sourceInfo; + } + else if (sourceInfo.equals(mBleBroadcastSourceInfo) != true) { + //toast Message + mBleBroadcastSourceInfo = sourceInfo; + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Update in Broadcast Source Information"); + toastString = "Source Info Update"; + } else { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":No Update to Source Information values"); + } + } else { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Ignore this case"); + } + if (toastString != null) { + Toast toast = Toast.makeText(mContext, toastString, Toast.LENGTH_SHORT); + toast.show(); + } + } + refresh(); + } + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onPreferenceChange" + newValue); + if (key.equals(KEY_UPDATE_BCAST_CODE)) { + EditTextPreference pref = (EditTextPreference)preference; + String code = (String)newValue; + //Use different flag for Broadcast pin + isBroadcastPINUpdated = true; + mBroadcastCode = (String)newValue; + } else if (key.equals(KEY_SOURCE_AUDIO_STATE)) { + BroadcastScanAssistanceUtils.debug(TAG, ">>Checked:" +newValue); + CharSequence[] getEntriesSeqence = + ((MultiSelectListPreference)preference).getEntries(); + Set valueSet = ((MultiSelectListPreference)preference).getValues(); + + String[] selectedStrings = new String[((Set) newValue).size()]; + + //noinspection unchecked + int j =0; + for (String value : (Set) newValue) { + selectedStrings[j] = value; + for (int i=0; i>Pin code updated: " + pref.getText()); + //Use different flag for Broadcast pin + mIsValueChanged = false; + isBroadcastPINUpdated = true; + mBroadcastCode = pref.getText(); + } else { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":unhandled preference"); + mIsValueChanged = false; + } + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onPreferenceClick" + mBleBroadcastSourceInfo); + mIsButtonRefreshOnly = true; + refresh(); + return true; + } + + @Override + public void onPause() { + super.onPause(); + mCachedDevice.unregisterCallback(this); + } + + @Override + public void onResume() { + super.onResume(); + mCachedDevice.registerCallback(this); + } + + String getEncryptionStatusString(int encryptionStatus) { + + switch(encryptionStatus) { + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_INVALID: + return "ENCRYPTION STATE UNKNOWN"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED: + return "UNENCRYPTED STREAMING"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED: + return "PIN UPDATE NEEDED"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_DECRYPTING: + return "DECRYPTING SUCCESSFULLY"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_BADCODE: + return "INCORRECT BROADCAST PIN"; + } + return "ENCRYPTION STATE UNKNOWN"; + } + + String getMetadataSyncStatusString(int metadataSyncStatus) { + + switch(metadataSyncStatus) { + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IDLE: + return "IDLE"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_INVALID: + return "UNKNOWN"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC: + return "IN SYNC"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST: + return "NO PAST"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ: + return "SYNCINFO NEEDED"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL: + return "SYNC FAIL"; + } + return "UNKNOWN"; + } + + String getAudioSyncStatusString(int audioSyncStatus) { + + switch(audioSyncStatus) { + case BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID: + return "UNKNOWN"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED: + return "NOT IN SYNC"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED: + return "IN SYNC"; + } + return "UNKNOWN"; + } + + private boolean isPinUpdatedNeeded() { + boolean ret = false; + + if (BroadcastScanAssistanceUtils.isLocalDevice(mBleBroadcastSourceInfo.getSourceDevice())) { + BroadcastScanAssistanceUtils.debug(TAG, "Local Device, Dont allow User to update PWD"); + return false; + } + if (mBleBroadcastSourceInfo.getEncryptionStatus() + == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { + ret = true; + } + + BroadcastScanAssistanceUtils.debug(TAG, "isPinUpdatedNeeded return" + ret); + return ret; + } + + /** + * Refreshes the state of the switches for all profiles, possibly adding or removing switches as + * needed. + */ + @Override + protected void refresh() { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":refresh: " + mBleBroadcastSourceInfo + " mSourceIndex" + mSourceInfoIndex); + mSourceIdPref.setSummary( + String.valueOf(mBleBroadcastSourceInfo.getSourceId())); + + BluetoothDevice dev = mBleBroadcastSourceInfo.getSourceDevice(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + String s = null; + if (dev != null && adapter != null) { + if (adapter.getAddress().equals(dev.getAddress())) + { + s = adapter.getName() + "(Self)"; + } else { + s = dev.getAlias(); + } + if (s == null) { + s = String.valueOf(dev.getAddress()); + } + } + if (s == null || s.equals(EMPTY_BD_ADDRESS)) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":NULL source device"); + s = "EMPTY_ENTRY"; + } + mSourceDevicePref.setSummary(s); + mSourceEncStatusPref.setSummary( + getEncryptionStatusString( + mBleBroadcastSourceInfo.getEncryptionStatus()) + ); + + if (mBleBroadcastSourceInfo.isEmptyEntry()) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Source Information seem to be Empty"); + if (mPAsyncCtrlNeeded) { + mSourceMetadataSyncSwitchPref.setEnabled(false); + } + mSourceAudioSyncSwitchPref.setEnabled(false); + mSourceUpdateBcastCodePref.setEnabled(false); + //Disable 'remove and update source Info' if It is empty entry + mSourceUpdateSourceInfoPref.setButton1Enabled(false); + mSourceUpdateSourceInfoPref.setButton2Enabled(false); + mSourceAudioSyncStatusPref.setEnabled(false); + mIsValueChanged = false; + } else { + //enable the Input controls + if (mPAsyncCtrlNeeded) { + mSourceMetadataSyncSwitchPref.setEnabled(true); + } + mSourceAudioSyncSwitchPref.setEnabled(true); + mSourceUpdateBcastCodePref.setEnabled(isPinUpdatedNeeded()); + + if (mIsButtonRefreshOnly != true) { + mSourceMetadataSyncStatusPref.setSummary( + getMetadataSyncStatusString(mBleBroadcastSourceInfo.getMetadataSyncState()) + ); + mSourceAudioSyncStatusPref.setSummary( + getAudioSyncStatusString(mBleBroadcastSourceInfo.getAudioSyncState()) + ); + mBisIndicies = mBleBroadcastSourceInfo.getBroadcastChannelsSyncStatus(); + if (mBisIndicies != null) { + String[] bisNames = new String[mBisIndicies.size()]; + boolean[] bisStatuses = new boolean[mBisIndicies.size()]; + Set hashSet = new HashSet(); + for (int i=0; i createPreferenceControllers(Context context) { + ArrayList controllers = new ArrayList<>(); + + if (mCachedDevice != null && mBleBroadcastSourceInfo != null) { + Lifecycle lifecycle = getSettingsLifecycle(); + controllers.add(new BleBroadcastSourceInfoDetailsController(context, this, mBleBroadcastSourceInfo, + mCachedDevice, mSourceInfoIndex, lifecycle)); + } + return controllers; + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java new file mode 100644 index 0000000000000000000000000000000000000000..50c4636a93e16ed8ca13faf655f196a9b1cff897 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BleBroadcastSourceInfo; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.UserManager; +import android.text.Html; +import android.text.TextUtils; +import android.util.Pair; +import android.util.TypedValue; +import android.view.View; +import android.widget.ImageView; +import android.util.Log; + +import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.widget.GearPreference; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.Integer; +import java.lang.String; +/** + * BleBroadcastSourceInfoPreference is the preference type used to display each + * Broadcast Source information stored in the Remote Scan delegator. + */ +public final class BleBroadcastSourceInfoPreference extends GearPreference implements + CachedBluetoothDevice.Callback { + private static final String TAG = "BleBroadcastSourceInfoPreference"; + + private static String EMPTY_BD_ADDR = "00:00:00:00:00:00"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SortType.TYPE_DEFAULT, + SortType.TYPE_FIFO}) + public @interface SortType { + int TYPE_DEFAULT = 1; + int TYPE_FIFO = 2; + } + + private final CachedBluetoothDevice mCachedDevice; + private BleBroadcastSourceInfo mBleSourceInfo; + private final Integer mIndex; + private final long mCurrentTime; + private final int mType; + + ///private String contentDescription = null; + //@VisibleForTesting + //boolean mNeedNotifyHierarchyChanged = false; + /* Talk-back descriptions for various BT icons */ + Resources mResources; + + public BleBroadcastSourceInfoPreference(Context context, CachedBluetoothDevice device, + BleBroadcastSourceInfo sourceInfo, + Integer index, @SortType int type) { + super(context, null); + mResources = getContext().getResources(); + mIndex = index; + + mCachedDevice = device; + mBleSourceInfo = sourceInfo; + mCachedDevice.registerCallback(this); + mCurrentTime = System.currentTimeMillis(); + mType = type; + + onDeviceAttributesChanged(); + } + + + @Override + protected boolean shouldHideSecondTarget() { + return (mBleSourceInfo == null); + } + + @Override + protected int getSecondTargetResId() { + return R.layout.preference_widget_gear; + } + + CachedBluetoothDevice getCachedDevice() { + return mCachedDevice; + } + + public BleBroadcastSourceInfo getBleBroadcastSourceInfo() { + return mBleSourceInfo; + } + + public void setBleBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { + mBleSourceInfo = srcInfo; + //refresh + onDeviceAttributesChanged(); + } + + Integer getSourceInfoIndex() { + return mIndex; + } + + @Override + protected void onPrepareForRemoval() { + super.onPrepareForRemoval(); + mCachedDevice.unregisterCallback(this); + } + + String formSyncSummaryString(BleBroadcastSourceInfo srcInfo) { + String metadataStatus = "Metadata Synced"; + String audioSyncStatus = "Audio Synced"; + + if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + metadataStatus = "Metadata Synced"; + } else { + metadataStatus = "Metadata not synced"; + } + + if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { + audioSyncStatus = "Audio Synced"; + } else { + audioSyncStatus = "Audio not synced"; + } + return metadataStatus + ", " + audioSyncStatus; + } + + public void onDeviceAttributesChanged() { + BluetoothDevice dev = mBleSourceInfo.getSourceDevice(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + String s = null; + if (dev != null && adapter != null) { + if (adapter.getAddress().equals(dev.getAddress())) + { + s = adapter.getName() + "(Self)"; + } else { + s = dev.getAlias(); + } + if (s == null) { + s = String.valueOf(dev.getAddress()); + } + } + if (s == null || s.equals(EMPTY_BD_ADDR)) { + BroadcastScanAssistanceUtils.debug(TAG, "seem to be an entry source Info"); + s = "EMPTY ENTRY"; + } + setTitle(s); + setIcon(R.drawable.ic_media_stream); + if (!mBleSourceInfo.isEmptyEntry()) { + //Show the status only If it is not an Empty Entry + setSummary(formSyncSummaryString(mBleSourceInfo)); + } else { + setSummary(""); + } + setVisible(true); + + // This could affect ordering, so notify that + notifyHierarchyChanged(); + } + + + + @Override + public boolean equals(Object o) { + if ((o == null) || !(o instanceof BleBroadcastSourceInfoPreference)) { + BroadcastScanAssistanceUtils.debug(TAG, "Not an Instance of BleBroadcastSourceInfoPreference:"); + return false; + } + BleBroadcastSourceInfo otherSrc = ((BleBroadcastSourceInfoPreference) o).mBleSourceInfo; + BroadcastScanAssistanceUtils.debug(TAG, "Comparing: " + mBleSourceInfo); + BroadcastScanAssistanceUtils.debug(TAG, "TO: " + otherSrc); + boolean ret = (mBleSourceInfo.getSourceId() == otherSrc.getSourceId()); + BroadcastScanAssistanceUtils.debug(TAG, "equals returns: " + ret); + + return ret; + } + + @Override + public int hashCode() { + return mBleSourceInfo.hashCode(); + } + + @Override + public int compareTo(Preference another) { + if (!(another instanceof BleBroadcastSourceInfoPreference)) { + // Rely on default sort + return super.compareTo(another); + } + + switch (mType) { + case SortType.TYPE_DEFAULT: + BroadcastScanAssistanceUtils.debug(TAG, ">>compareTo"); + return mIndex > ((BleBroadcastSourceInfoPreference) another).getSourceInfoIndex() ? 1 : -1; + case SortType.TYPE_FIFO: + return mCurrentTime > ((BleBroadcastSourceInfoPreference) another).mCurrentTime ? 1 : -1; + default: + return super.compareTo(another); + } + } + + void onClicked() { + Context context = getContext(); + + final MetricsFeatureProvider metricsFeatureProvider = + FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..8f9a4fb50fc0dd06eb249166fe832b42d1041835 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import androidx.preference.Preference; + +/** + * Callback to add or remove {@link Preference} in Ble broadcast source info + * entries. + */ +public interface BleBroadcastSourceInfoPreferenceCallback { + /** + * Called when a Ble broadcast sourc Information is added + * @param preference present the device + */ + void onBroadcastSourceInfoAdded(Preference preference); + + /** + * Called when a Ble broadast source Information is removed + * @param preference present the device + */ + void onBroadcastSourceInfoRemoved(Preference preference); +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..6baa20a687a103d84591fb9bb9e55f4fd5939c01 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.content.Context; +import java.util.Iterator; +import android.os.Bundle; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.widget.GearPreference; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.VendorCachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.lang.Integer; + +/** + * Update the Ble broadcast source Info preference entries. It retrieves the Bluetooth broadcast source + * information using CachedBluetoothDevice object from setting library + * {@link BluetoothCallback}. It notifies the upper level whether to add/remove the preference + * through {@link BleBroadcastSourceInfoPreferenceCallback} + * + * In {@link BleBroadcastSourceInfoUpdater}, it uses {@link BluetoothDeviceFilter.Filter} to detect + * whether the {@link CachedBluetoothDevice} is relevant. + */ +public abstract class BleBroadcastSourceInfoUpdater implements CachedBluetoothDevice.Callback, + BluetoothCallback { + private static final String TAG = "BleBroadcastSourceInfoUpdater"; + private static final boolean DBG = false; + + protected final BleBroadcastSourceInfoPreferenceCallback mBleSourceInfoPreferenceCallback; + protected final Map mPreferenceMap; + protected Context mPrefContext; + protected DashboardFragment mFragment; + protected final CachedBluetoothDevice mCachedDevice; + protected final VendorCachedBluetoothDevice mVendorCachedDevice; + private LocalBluetoothManager mLocalManager; + + final GearPreference.OnGearClickListener mSourceInfoEntryListener = pref -> { + launchSourceInfoDetails(pref); + }; + + public BleBroadcastSourceInfoUpdater(Context context, DashboardFragment fragment, + BleBroadcastSourceInfoPreferenceCallback aBleSourceInfoPreferenceCallback, + CachedBluetoothDevice device) { + this(fragment, aBleSourceInfoPreferenceCallback ,device); + } + + BleBroadcastSourceInfoUpdater(DashboardFragment fragment, + BleBroadcastSourceInfoPreferenceCallback aBleSourceInfoPreferenceCallback, + CachedBluetoothDevice device) { + mCachedDevice = device; + LocalBluetoothManager mgr = Utils.getLocalBtManager(mPrefContext); + LocalBluetoothProfileManager profileManager = mgr.getProfileManager(); + mVendorCachedDevice = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(device, profileManager); + mFragment = fragment; + mBleSourceInfoPreferenceCallback = aBleSourceInfoPreferenceCallback; + mPreferenceMap = new HashMap(); + mLocalManager = Utils.getLocalBtManager(mPrefContext); + mLocalManager.getEventManager().registerCallback(this); + } + + /** + * Register the bluetooth event callback and update the list + */ + public void registerCallback() { + mCachedDevice.registerCallback(this); + forceUpdate(); + } + + /** + * Unregister the bluetooth event callback + */ + public void unregisterCallback() { + mCachedDevice.unregisterCallback(this); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + BroadcastScanAssistanceUtils.debug(TAG, "onBluetoothStateChanged"); + if (bluetoothState == BluetoothAdapter.STATE_OFF) { + removeAllBleBroadcastSourceInfosFromPreference(); + } + //forceUpdate(); + } + + /** + * Force to update the list of bluetooth devices + */ + public void forceUpdate() { + if (mCachedDevice != null && + mVendorCachedDevice.getNumberOfBleBroadcastReceiverStates() > 0) { + final Map srcInfos = + mVendorCachedDevice.getAllBleBroadcastreceiverStates(); + if (srcInfos == null) { + Log.e(TAG, "srcInfos is null"); + return; + } + for (Map.Entry entry: srcInfos.entrySet()) { + update(entry.getKey(), entry.getValue()); + } + } else { + BroadcastScanAssistanceUtils.debug(TAG, "remove all the preferences as there are no rcvr states"); + removeAllBleBroadcastSourceInfosFromPreference(); + } + } + + public void removeAllBleBroadcastSourceInfosFromPreference() { + Iterator> entries = mPreferenceMap.entrySet().iterator(); + while (entries.hasNext()) { + //for (Map.Entry entry: mPreferenceMap.entrySet()) { + Map.Entry entry = entries.next(); + //removePreference(entry.getKey(), entry.getValue()); + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoRemoved(entry.getValue()); + } + mPreferenceMap.clear(); + } + + @Override + public void onDeviceAttributesChanged() { + BroadcastScanAssistanceUtils.debug(TAG, "onDeviceAttributesChanged"); + forceUpdate(); + } + + /** + * Set the context to generate the {@link Preference}, so it could get the correct theme. + */ + public void setPrefContext(Context context) { + mPrefContext = context; + } + + /** + * Update whether to show {@link CachedBluetoothDevice} in the list. + */ + protected void update(Integer index, BleBroadcastSourceInfo sourceInfo) { + addPreference(index, sourceInfo); + } + + /** + * Add the {@link Preference} that represents the {@code cachedDevice} + */ + protected void addPreference(Integer index, BleBroadcastSourceInfo sourceInfo) { + final BluetoothDevice device = sourceInfo.getSourceDevice(); + final byte sourceId = sourceInfo.getSourceId(); + if (mPreferenceMap.containsKey(index) == false) { + BroadcastScanAssistanceUtils.debug(TAG, "source info addition"); + BleBroadcastSourceInfoPreference sourceInfoPreference = + new BleBroadcastSourceInfoPreference(mPrefContext, + mCachedDevice, + sourceInfo, + index, + BleBroadcastSourceInfoPreference.SortType.TYPE_DEFAULT); + sourceInfoPreference.setOnGearClickListener(mSourceInfoEntryListener); + if (this instanceof Preference.OnPreferenceClickListener) { + sourceInfoPreference.setOnPreferenceClickListener( + (Preference.OnPreferenceClickListener)this); + } + BroadcastScanAssistanceUtils.debug(TAG, "source info newly added: " + index); + mPreferenceMap.put(index, sourceInfoPreference); + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoAdded(sourceInfoPreference); + } else { + BleBroadcastSourceInfoPreference pref = (BleBroadcastSourceInfoPreference)mPreferenceMap.get(index); + BleBroadcastSourceInfo currentSi = pref.getBleBroadcastSourceInfo(); + if (currentSi != null && currentSi.equals(sourceInfo)) { + BroadcastScanAssistanceUtils.debug(TAG, "No change in SI" + index); + } else { + BroadcastScanAssistanceUtils.debug(TAG, "source info Updated: " + index); + pref.setBleBroadcastSourceInfo (sourceInfo); + + /*mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoRemoved(mPreferenceMap.get(index)); + mPreferenceMap.remove(index); + + BleBroadcastSourceInfoPreference sourceInfoPreference = + new BleBroadcastSourceInfoPreference(mPrefContext, + mCachedDevice, + sourceInfo, + index, + BleBroadcastSourceInfoPreference.SortType.TYPE_DEFAULT); + sourceInfoPreference.setOnGearClickListener(mSourceInfoEntryListener); + if (this instanceof Preference.OnPreferenceClickListener) { + sourceInfoPreference.setOnPreferenceClickListener( + (Preference.OnPreferenceClickListener)this); + } + BroadcastScanAssistanceUtils.debug(TAG, "source info added again: " + index); + mPreferenceMap.put(index, sourceInfoPreference); + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoAdded(sourceInfoPreference);*/ + } + } + } + + /** + * Remove the {@link Preference} that represents the {@code cachedDevice} + */ + protected void removePreference(int index, Preference pref) { + if (mPreferenceMap.containsKey(index)) { + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoRemoved(mPreferenceMap.get(index)); + mPreferenceMap.remove(index); + } + } + + /** + * Get {@link CachedBluetoothDevice} from {@link Preference} and it is used to init + * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment} + */ + protected void launchSourceInfoDetails(Preference preference) { + final BleBroadcastSourceInfo srcInfo = + ((BleBroadcastSourceInfoPreference) preference).getBleBroadcastSourceInfo(); + if (srcInfo == null) { + return; + } + final int index = ((BleBroadcastSourceInfoPreference) preference).getSourceInfoIndex(); + final Bundle args = new Bundle(); + args.putString(BleBroadcastSourceInfoDetailsFragment.KEY_DEVICE_ADDRESS, + mCachedDevice.getAddress()); + args.putParcelable(BleBroadcastSourceInfoDetailsFragment.KEY_SOURCE_INFO, + srcInfo); + args.putInt(BleBroadcastSourceInfoDetailsFragment.KEY_SOURCE_INFO_INDEX, + index); + + new SubSettingLauncher(mFragment.getContext()) + .setDestination(BleBroadcastSourceInfoDetailsFragment.class.getName()) + .setArguments(args) + .setTitleRes(R.string.source_info_details_title) + .setSourceMetricsCategory(mFragment.getMetricsCategory()) + .launch(); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java new file mode 100644 index 0000000000000000000000000000000000000000..29f565c41fb94a48953ce7293e4822ba80551109 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java @@ -0,0 +1,247 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothBroadcast; +import android.content.Context; +import android.util.Log; +import android.os.SystemProperties; +import android.os.Handler; +import android.os.Message; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.BroadcastProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settings.connecteddevice.BluetoothDashboardFragment; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import androidx.annotation.Keep; + +@Keep +public class BluetoothBroadcastEnableController extends TogglePreferenceController + implements LifecycleObserver, OnResume, OnPause, OnDestroy, BluetoothCallback { + + public static final String TAG = "BluetoothBroadcastEnableController"; + public static final int BROADCAST_AUDIO_MASK = 0x04; + public static final String BLUETOOTH_LE_AUDIO_MASK_PROP = "persist.vendor.service.bt.adv_audio_mask"; + public static final String KEY_BROADCAST_ENABLE = "bluetooth_screen_broadcast_enable"; + private RestrictedSwitchPreference mPreference = null; + private BluetoothAdapter mBluetoothAdapter; + private boolean mState = false; + private boolean reset_pending = false; + private Context mContext; + private BroadcastProfile mBapBroadcastProfile = null; + private boolean isBluetoothLeBroadcastAudioSupported = false; + private boolean mCallbacksRegistered = false; + private LocalBluetoothManager mManager = null; + public BluetoothBroadcastEnableController(Context context, String key) { + super(context, key); + Log.d(TAG, "Constructor() with key"); + Init(context); + } + + private void Init(Context context) { + mContext = context; + int leAudioMask = SystemProperties.getInt(BLUETOOTH_LE_AUDIO_MASK_PROP, 0); + isBluetoothLeBroadcastAudioSupported = ((leAudioMask & BROADCAST_AUDIO_MASK) == BROADCAST_AUDIO_MASK); + if(isBluetoothLeBroadcastAudioSupported){ + mManager = Utils.getLocalBtManager(context); + mBapBroadcastProfile = (BroadcastProfile) mManager.getProfileManager().getBroadcastProfile(); + if (!mCallbacksRegistered) { + Log.d(TAG, "Registering EventManager callbacks"); + mCallbacksRegistered = true; + mManager.getEventManager().registerCallback(this); + } + } + Log.d(TAG, "Init done"); + } + + private void updateState(boolean newState) { + Log.d(TAG, "updateState req " + Boolean.toString(newState)); + if (newState != mState) { + if (mPreference != null) mPreference.setEnabled(false); + Log.d(TAG, "updateState to " + Boolean.toString(newState)); + mBapBroadcastProfile.setBroadcastMode(newState); + } + } + + private void onStateChanged(boolean newState) { + Log.d(TAG, "onStateChanged " + Boolean.toString(newState)); + mState = newState; + if (mPreference != null) mPreference.setChecked(mState); + if (mState == false && reset_pending == true) { + reset_pending = false; + updateState(true); + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "BT state, msg = " + Integer.toString(msg.what)); + switch (msg.what) { + case BluetoothAdapter.STATE_ON: + if (mPreference != null) mPreference.setEnabled(true); + mBapBroadcastProfile = (BroadcastProfile) mManager.getProfileManager().getBroadcastProfile(); + break; + case BluetoothAdapter.STATE_TURNING_ON: + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + reset_pending = false; + onStateChanged(false); + mBapBroadcastProfile = null; + if (mPreference != null) mPreference.setEnabled(false); + break; + } + } + }; + + @Override + public void onBluetoothStateChanged(int newBtState) { + Log.d(TAG, "onBluetoothStateChanged" + Integer.toString(newBtState)); + + switch (newBtState) { + case BluetoothAdapter.STATE_ON: + mHandler.sendMessageDelayed(mHandler.obtainMessage(newBtState), 200); + break; + case BluetoothAdapter.STATE_TURNING_ON: + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + mHandler.sendMessage(mHandler.obtainMessage(newBtState)); + break; + } + } + + @Override + public void onBroadcastStateChanged(int newBapState) { + Log.d(TAG, "onBroadcastStateChanged" + Integer.toString(newBapState)); + + switch (newBapState) { + case BluetoothBroadcast.STATE_ENABLED: + if (mPreference != null) mPreference.setEnabled(true); + onStateChanged(true); + break; + case BluetoothBroadcast.STATE_DISABLED: + if (mPreference != null) mPreference.setEnabled(true); + onStateChanged(false); + break; + } + } + + @Override + public void onBroadcastKeyGenerated() { + Log.d(TAG, "onBroadcastKeyGenerated"); + // Encryption key got updated. Reset BAP? + if (mState == true) { + reset_pending = true; + updateState(false); + } + } + + @Override + public String getPreferenceKey() { + Log.d(TAG, "getPreferenceKey"); + return KEY_BROADCAST_ENABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + Log.d(TAG, "displayPreference"); + mPreference = screen.findPreference(getPreferenceKey()); + if(isBluetoothLeBroadcastAudioSupported) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + onBluetoothStateChanged(mBluetoothAdapter.getState()); + if ((mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) && + (mBapBroadcastProfile.isProfileReady())) { + int bapState = mBapBroadcastProfile.getBroadcastStatus(); + Log.d(TAG, "get status done"); + if ((bapState == BluetoothBroadcast.STATE_ENABLED) || + (bapState == BluetoothBroadcast.STATE_STREAMING)) + onStateChanged(true); + else + onStateChanged(false); + } + } else { + mPreference.setVisible(false); + } + } + + @Override + public boolean isChecked() { + return mState; + } + + @Override + public boolean setChecked(boolean isChecked) { + updateState(isChecked); + return true; + } + + @Override + public int getAvailabilityStatus() { + Log.d(TAG, "getAvailabilityStatus"); + if(isBluetoothLeBroadcastAudioSupported) { + return AVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + @Override + public boolean hasAsyncUpdate() { + Log.d(TAG, "hasAsyncUpdate"); + return true; + } + + @Override + public boolean isPublicSlice() { + Log.d(TAG, "isPublicSlice"); + return true; + } + + @Override + public void onResume() { + Log.d(TAG, "onResume"); + } + + @Override + public void onPause() { + Log.d(TAG, "onPause"); + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestory"); + mCallbacksRegistered = false; + if (mManager != null) + mManager.getEventManager().unregisterCallback(this); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java new file mode 100644 index 0000000000000000000000000000000000000000..500d9414815914075e60c6e59a6fa02c28468a04 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java @@ -0,0 +1,237 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + + +package com.android.settings.bluetooth; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothBroadcast; +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Log; +import android.view.View; +import android.view.LayoutInflater; +import android.text.TextUtils; +import android.widget.TextView; +import android.bluetooth.BluetoothAdapter; +import android.os.Handler; +import android.os.SystemProperties; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceScreen; + +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.BroadcastProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.RestrictedPreference; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import androidx.annotation.Keep; + +/** + * Controller that shows Pin for BLE Broadcast Audio + */ +@Keep +public class BluetoothBroadcastPinController extends BasePreferenceController + implements OnDestroy, BluetoothCallback { + public static final String TAG = "BluetoothBroadcastPinController"; + public static final int BROADCAST_AUDIO_MASK = 0x04; + public static final String BLUETOOTH_LE_AUDIO_MASK_PROP = "persist.vendor.service.bt.adv_audio_mask"; + public static final String KEY_BROADCAST_AUDIO_PIN = "bluetooth_screen_broadcast_pin_configure"; + + private BluetoothAdapter mBluetoothAdapter; + private Fragment mFragment = null; + private MetricsFeatureProvider mMetricsFeatureProvider; + @VisibleForTesting + RestrictedPreference mPreference; + private Context mContext; + + private boolean isBluetoothLeBroadcastAudioSupported = false; + private boolean mCallbacksRegistered = false; + private LocalBluetoothManager mManager = null; + private Handler mHandler; + private Runnable mRunnable = new Runnable() { + @Override + public void run() { + onBroadcastKeyGenerated(); + } + }; + + public BluetoothBroadcastPinController(Context context) { + super(context, KEY_BROADCAST_AUDIO_PIN); + int leAudioMask = SystemProperties.getInt(BLUETOOTH_LE_AUDIO_MASK_PROP, 0); + isBluetoothLeBroadcastAudioSupported = ((leAudioMask & BROADCAST_AUDIO_MASK) == BROADCAST_AUDIO_MASK); + Log.d(TAG, "Constructor()"); + mContext = context; + mHandler = new Handler(); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if(isBluetoothLeBroadcastAudioSupported) { + mManager = Utils.getLocalBtManager(context); + if (!mCallbacksRegistered) { + Log.d(TAG, "Registering EventManager callbacks"); + mCallbacksRegistered = true; + mManager.getEventManager().registerCallback(this); + } + } + } + + public BluetoothBroadcastPinController(Context context, PreferenceFragmentCompat fragment, String prefKey) { + super(context, KEY_BROADCAST_AUDIO_PIN); + Log.d(TAG, "PinController()" + prefKey); + mFragment = fragment; + mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + } + + @VisibleForTesting + public void setFragment(Fragment fragment) { + Log.d(TAG, "setFragment"); + mFragment = fragment; + } + + @Override + public int getAvailabilityStatus() { + Log.d(TAG, "getAvailabilityStatus"); + if(isBluetoothLeBroadcastAudioSupported) { + return AVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + @Override + public String getPreferenceKey() { + return KEY_BROADCAST_AUDIO_PIN; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + Log.d(TAG, "displayPreference"); + mPreference = screen.findPreference(getPreferenceKey()); + if(isBluetoothLeBroadcastAudioSupported) { + onBroadcastKeyGenerated(); + } else { + mPreference.setVisible(false); + } + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + Log.d(TAG, "PinController: handlePreferenceTreeClick"); + if (KEY_BROADCAST_AUDIO_PIN.equals(preference.getKey())) { + Log.d(TAG, "PinController: handlePreferenceTreeClick true"); + new BluetoothBroadcastPinFragment() + .show(mFragment.getFragmentManager(), "PinFragment"); + return true; + } + + return false; + } + + @Override + public void onBluetoothStateChanged(int newBtState) { + Log.d(TAG, "onBluetoothStateChanged" + Integer.toString(newBtState)); + int delay = 0; + switch (newBtState) { + case BluetoothAdapter.STATE_ON: + delay = 200; + case BluetoothAdapter.STATE_OFF: + mHandler.postDelayed(mRunnable, delay); + break; + } + } + + private String convertBytesToString(byte[] pin) { + if (pin.length != 16) { + Log.e (TAG, "Not 16 bytes ++++++++++++"); + return ""; + } + byte[] temp = new byte[16]; + int i = 0, j = 0; + // Reverse the pin and discard the padding + for (i = 0; i < 16; i++) { + if (pin[15-i] == 0) break; + temp[j++] = pin[15-i]; + } + String str; + if (j == 0) + str = new String(""); // unencrypted + else + str = new String(Arrays.copyOfRange(temp,0,j), StandardCharsets.UTF_8); + Log.d(TAG, "Pin: " + str); + return str; + } + + @Override + public void onBroadcastKeyGenerated() { + Log.d(TAG, "onBroadcastKeyGenerated"); + String summary = "Broadcast code: "; + String keyStr = "Unavailable"; + + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + LocalBluetoothProfileManager profileManager = mManager.getProfileManager(); + BroadcastProfile bapProfile = (BroadcastProfile) profileManager.getBroadcastProfile(); + if ((mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) && + (bapProfile.isProfileReady())) { + byte[] key = bapProfile.getEncryptionKey(); + // Key can only be 16 byte long + if (key.length == 16) { + for(int i = 0; i mRadioButtonIds = new ArrayList<>(); + private List mRadioButtonStrings = new ArrayList<>(); + + private int getDialogTitle() { + return R.string.bluetooth_broadcast_pin_configure_dialog; + } + + private void updatePinConfiguration() { + Log.d(TAG, "updatePinConfiguration with " + Integer.toString(mUserSelectedPinConfiguration)); + if (mUserSelectedPinConfiguration == -1) { + Log.e(TAG, "no pin selected"); + return; + } + // Call lower layer to generate new pin + LocalBluetoothManager mManager = Utils.getLocalBtManager(mContext); + LocalBluetoothProfileManager profileManager = mManager.getProfileManager(); + BroadcastProfile bapProfile = (BroadcastProfile) profileManager.getBroadcastProfile(); + if (mUserSelectedPinConfiguration != 0) + bapProfile.setEncryption(true, mUserSelectedPinConfiguration, false); + else + bapProfile.setEncryption(false, mUserSelectedPinConfiguration, false); + } + + @Override + public void onAttach(Context context) { + Log.d(TAG, "onAttach"); + super.onAttach(context); + mContext = context; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + Log.d(TAG, "onActivityCreated"); + super.onActivityCreated(savedInstanceState); + //Dialog mDialog = onCreateDialog(new Bundle()); + //this.show(this.getActivity().getSupportFragmentManager(), "PinFragment"); + } + + /* + public void show() { + Log.e(TAG, "show"); + this.show(this.getActivity().getSupportFragmentManager(), "PinFragment"); + } + */ + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + //String deviceName = getDeviceName(); + Log.d(TAG, "onCreateDialog - enter"); + if (savedInstanceState != null) { + Log.e(TAG, "savedInstanceState != null"); + } + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(getDialogTitle()) + .setView(createDialogView()) + .setPositiveButton(R.string.okay, (dialog, which) -> { + //setDeviceName(mDeviceNameView.getText().toString().trim()); + updatePinConfiguration(); + }) + .setNegativeButton(android.R.string.cancel, null); + mAlertDialog = builder.create(); + Log.d(TAG, "onCreateDialog - exit"); + return mAlertDialog; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.BLUETOOTH_FRAGMENT; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + Log.d(TAG, "onSaveInstanceState"); + } + + private int getRadioButtonGroupId() { + return R.id.bluetooth_broadcast_pin_config_radio_group; + } + + private void setCurrentPin(String pin) { + mCurrentPin = pin; + } + + private String getCurrentPin() { + return mCurrentPin; + } + + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + Log.d(TAG, "Index changed to " + checkedId); + // radioButton = (RadioButton) view.findViewById(checkedId); + int index = mRadioButtonIds.indexOf(checkedId); + Log.d(TAG, "index"); + String[] stringArrayValues = getContext().getResources().getStringArray( + R.array.bluetooth_broadcast_pin_config_values); + mUserSelectedPinConfiguration = Integer.parseInt(stringArrayValues[index]); + Log.d(TAG, "Selected Pin Configuration " + Integer.toString(mUserSelectedPinConfiguration)); + } + + private View createDialogView() { + Log.d(TAG, "onCreateDialogView - enter"); + final LayoutInflater layoutInflater = (LayoutInflater)getActivity() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = layoutInflater.inflate(R.xml.bluetooth_broadcast_pin_config, null); + + final RadioGroup radioGroup = (RadioGroup) view.findViewById(getRadioButtonGroupId()); + if (radioGroup == null) { + Log.e (TAG, "Not able to find RadioGroup"); + return null; + } + radioGroup.clearCheck(); + radioGroup.setOnCheckedChangeListener(this); + + // Fill up the Radio Group + mRadioButtonIds.add(R.id.bluetooth_broadcast_pin_unencrypted); + mRadioButtonIds.add(R.id.bluetooth_broadcast_pin_4); + mRadioButtonIds.add(R.id.bluetooth_broadcast_pin_16); + String[] stringArray = getContext().getResources().getStringArray( + R.array.bluetooth_broadcast_pin_config_titles); + for (int i = 0; i < stringArray.length; i++) { + mRadioButtonStrings.add(stringArray[i]); + } + RadioButton radioButton; + for (int i = 0; i < mRadioButtonStrings.size(); i++) { + radioButton = (RadioButton) view.findViewById(mRadioButtonIds.get(i)); + if (radioButton == null) { + Log.e(TAG, "Unable to show dialog by no radio button:" + mRadioButtonIds.get(i)); + return null; + } + radioButton.setText(mRadioButtonStrings.get(i)); + radioButton.setEnabled(true); + } + + mCurrentPinView = (TextView) view.findViewById(R.id.bluetooth_broadcast_current_pin); + //mCurrentPinView.setText("Current Pin is " + getCurrentPin()); + Log.d(TAG, "onCreateDialogView - exit"); + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(TAG, "onDestroy"); + mAlertDialog = null; + mOkButton = null; + mCurrentPinView = null; + mRadioButtonIds = new ArrayList<>(); + mRadioButtonStrings = new ArrayList<>(); + mUserSelectedPinConfiguration = -1; + } + + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume"); + if (mOkButton == null) { + if (mAlertDialog != null) { + mOkButton = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE); + mOkButton.setEnabled(true); + } else { + Log.d(TAG, "onResume: mAlertDialog is null"); + } + } + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java new file mode 100644 index 0000000000000000000000000000000000000000..110d9b7b1cbf5560d3923d6cf36c04ec0d5803c6 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java @@ -0,0 +1,52 @@ +/* + *Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.content.Context; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.settings.bluetooth.BleBroadcastSourceInfoPreferenceCallback; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * Maintain and update saved bluetooth devices(bonded but not connected) + */ +public class BluetoothBroadcastSourceInfoEntries extends BleBroadcastSourceInfoUpdater + implements Preference.OnPreferenceClickListener { + private static final String TAG = "BluetoothBroadcastSourceInfoEntries"; + + + public BluetoothBroadcastSourceInfoEntries(Context context, DashboardFragment fragment, + BleBroadcastSourceInfoPreferenceCallback bleBroadcastSourceInfoPreferenceCallback, + CachedBluetoothDevice device) { + super(context, fragment, bleBroadcastSourceInfoPreferenceCallback, device); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + final BleBroadcastSourceInfo srcInfo = ((BleBroadcastSourceInfoPreference) preference) + .getBleBroadcastSourceInfo(); + BroadcastScanAssistanceUtils.debug(TAG, "onPreferenceClick: " + srcInfo); + return true; + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java new file mode 100644 index 0000000000000000000000000000000000000000..536bc1bfb82a7eff7f7ef5c53b23ec54ffc31b5f --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.content.Context; + +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.ActionButtonsPreference; +import com.android.settingslib.bluetooth.BCProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import android.util.Log; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import com.android.settings.core.SubSettingLauncher; +import android.app.settings.SettingsEnums; +import android.os.Bundle; +import android.bluetooth.BluetoothProfile; +/** + * This class adds two buttons: one to connect/disconnect from a device (depending on the current + * connected state), and one to "Search for LE audio Broadcast sources" around. + */ +public class BluetoothDetailsAddSourceButtonController extends BluetoothDetailsController + implements CachedBluetoothDevice.Callback { + private static final String KEY_ACTION_BUTTONS = "sync_helper_buttons"; + private static final String TAG = "BluetoothDetailsAddSourceButtonController"; + private boolean mIsConnected = false; + + private ActionButtonsPreference mActionButtons; + protected LocalBluetoothProfileManager mProfileManager; + private LocalBluetoothManager mLocalBluetoothManager; + private BCProfile mBCProfile = null; + + + public BluetoothDetailsAddSourceButtonController(Context context, PreferenceFragmentCompat fragment, + CachedBluetoothDevice device, Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + device.registerCallback(this); + + } + + private void onAddLESourcePressed() { + final Bundle args = new Bundle(); + args.putString(BluetoothSADetail.KEY_DEVICE_ADDRESS, + mCachedDevice.getDevice().getAddress()); + args.putShort(BluetoothSADetail.KEY_GROUP_OP, + (short)0); + + new SubSettingLauncher(mContext) + .setDestination(BluetoothSADetail.class.getName()) + .setArguments(args) + .setTitleRes(R.string.bluetooth_search_broadcasters) + .setSourceMetricsCategory(SettingsEnums.BLUETOOTH_DEVICE_PICKER) + .launch(); + } + + @Override + public void onDeviceAttributesChanged() { + refresh(); + } + + @Override + protected void init(PreferenceScreen screen) { + BroadcastScanAssistanceUtils.debug(TAG, "init"); + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mProfileManager = mLocalBluetoothManager.getProfileManager(); + mBCProfile = (BCProfile)mProfileManager.getBCProfile(); + + mActionButtons = ((ActionButtonsPreference) screen.findPreference( + getPreferenceKey())) + .setButton1Text(R.string.add_source_button_text) + .setButton1Icon(R.drawable.ic_add_24dp) + .setButton1OnClickListener((view) -> onAddLESourcePressed()) + .setButton1Enabled(false) + ; + } + + @Override + protected void refresh() { + BroadcastScanAssistanceUtils.debug(TAG, "refresh"); + if (mBCProfile != null) { + mIsConnected = mBCProfile.getConnectionStatus(mCachedDevice.getDevice()) == BluetoothProfile.STATE_CONNECTED; + } + if (mIsConnected) { + mActionButtons + .setButton1Enabled(true); + } else { + BroadcastScanAssistanceUtils.debug(TAG, "Bass is not connected for thsi device>>"); + mActionButtons + .setButton1Enabled(false); + } + } + + @Override + public String getPreferenceKey() { + return KEY_ACTION_BUTTONS; + } + +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java new file mode 100644 index 0000000000000000000000000000000000000000..174332cf2683aa0b45c849e81351920c27a9dba4 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; +import android.text.Html; +import android.widget.EditText; +import android.widget.RadioButton; +import android.view.View; +import android.widget.RadioGroup; +import android.bluetooth.le.ScanResult; +import android.bluetooth.IBluetoothManager; +import java.util.Arrays; +import java.util.Map; +import java.util.List; +import android.bluetooth.le.ScanRecord; +import android.app.Activity; + +import androidx.appcompat.app.AlertDialog; +import android.content.DialogInterface; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.VendorCachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.BCProfile; + +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; + +import com.android.settingslib.search.Indexable; +import com.android.settingslib.widget.FooterPreference; +import androidx.preference.Preference; +import android.widget.ListView; +import android.text.BidiFormatter; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.widget.CheckedTextView; + +/** + * BluetoothSADetail is a page to scan bluetooth devices and pair them. + */ +public class BluetoothSADetail extends DeviceListPreferenceFragment implements + Indexable { + private static final String TAG = "BluetoothSADetail"; + private static final boolean DBG = true; + + public static final String KEY_DEVICE_ADDRESS = "device_address"; + public static final String KEY_GROUP_OP = "group_op"; + static final String KEY_AVAIL_LE_AUDIO_SOURCES = "available_audio_sources"; + static final String KEY_FOOTER_PREF = "footer_preference"; + static final String SCAN_DEL_NAME = "Scan Delegator"; + + BluetoothProgressCategory mAvailableDevicesCategory; + FooterPreference mFooterPreference; + + private AlertDialog mScanAssistDetailsDialog; + private boolean mInitialScanStarted; + private CachedBluetoothDevice mCachedDevice; + Preference mScanDelegatorName; + String mSyncState; + BleBroadcastAudioScanAssistManager mScanAssistManager; + protected LocalBluetoothProfileManager mProfileManager; + String mBroadcastCode; + Context mContext; + CachedBluetoothDevice clickedDevice = null; + String mBroadcastPinCode = null; + boolean mScanning = true; + boolean mGroupOperation = false; + AlertDialog mCommonMsgDialog = null; + + private String getBluetoothName(BluetoothDevice dev) { + String aliasName = null; + if (dev == null) { + aliasName = SCAN_DEL_NAME; + } else { + aliasName = dev.getAlias(); + aliasName = TextUtils.isEmpty(aliasName) ? dev.getAddress() : aliasName; + if (aliasName == null) { + aliasName = SCAN_DEL_NAME; + } + } + BroadcastScanAssistanceUtils.debug(TAG, "getBluetoothName returns" + aliasName); + return aliasName; + } + + private int getSourceSelectionErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_selection_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE: + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE: + errorMessage = R.string.bluetooth_source_selection_error_src_unavail_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED: + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID: + errorMessage = R.string.bluetooth_source_selection_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION : + errorMessage = R.string.bluetooth_source_dup_addition_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT : + errorMessage = R.string.bluetooth_source_no_empty_slot_error_message; + break; + } + return errorMessage; + } + + private int getSourceAdditionErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_addition_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION : + errorMessage = R.string.bluetooth_source_dup_addition_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT : + errorMessage = R.string.bluetooth_source_no_empty_slot_error_message; + break; + } + return errorMessage; + } + + private int getSourceRemovalErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_removal_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL : + errorMessage = R.string.bluetooth_source_removal_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP : + errorMessage = R.string.bluetooth_source_remove_invalid_group_op; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID : + errorMessage = R.string.bluetooth_source_remove_invalid_src_id; + break; + } + return errorMessage; + } + + private int getSourceUpdateErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_update_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL : + errorMessage = R.string.bluetooth_source_update_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP : + errorMessage = R.string.bluetooth_source_update_invalid_group_op; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID : + errorMessage = R.string.bluetooth_source_update_invalid_src_id; + break; + } + return errorMessage; + } + + BleBroadcastAudioScanAssistCallback mScanAssistCallback = new BleBroadcastAudioScanAssistCallback() { + DialogInterface.OnClickListener commonMessageListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + BroadcastScanAssistanceUtils.debug(TAG, ">>OK clicked"); + if (mCommonMsgDialog != null) { + mCommonMsgDialog.dismiss(); + } + finish(); + } + }; + public void onBleBroadcastSourceFound(ScanResult res) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastSourceFound" + res.getDevice()); + + CachedBluetoothDevice cachedDevice = mLocalManager.getCachedDeviceManager().findDevice(res.getDevice()); + + if (cachedDevice != null) { + BroadcastScanAssistanceUtils.debug(TAG, "seems like CachedDevice entry already present for this device"); + } else { + //Create a Device entry for this, + //If this is randon Address, there would new CachedDevice Entry for this random address Instance + //However this wont have the name + cachedDevice = mLocalManager.getCachedDeviceManager().addDevice(res.getDevice()); + //udate the Name for this device from ADV: HACK + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (res.getDevice().getAddress().equals(adapter.getAddress())) { + BroadcastScanAssistanceUtils.debug(TAG, "Self DEVICE:"); + } else { + ScanRecord rec = res.getScanRecord(); + if (rec != null && rec.getDeviceName() != null) { + String s = rec.getDeviceName(); + BroadcastScanAssistanceUtils.debug(TAG,"setting name as " + s); + cachedDevice.setName(s); + } + } + } + + BluetoothDevicePreference pref = mDevicePreferenceMap.get(cachedDevice); + if (pref != null) { + //If the Prefernce alread Created, just update the + //Scan Result + //pref.SetScanResult(res); + BroadcastScanAssistanceUtils.debug(TAG, "Preference is already present" + res.getDevice()); + return; + } + // Prevent updates while the list shows one of the state messages + if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) return; + //if (mFilter.matches(cachedDevice.getDevice())) { + createDevicePreference(cachedDevice); + //} + // + VendorCachedBluetoothDevice vDev = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(cachedDevice, mProfileManager); + vDev.setScanResult(res); + }; + + public void onBleBroadcastSourceSelected(BluetoothDevice rcvr, int status, + List broadcastSourceIndicies) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastSourceSelected" + status + "sel indicies:" + broadcastSourceIndicies); + if (status == BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + launchSyncAndBroadcastIndexOptions(broadcastSourceIndicies); + } else { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, rcvr.getName(), + getSourceSelectionErrMessage(status), commonMessageListener); + + } + }; + + public void onBleBroadcastAudioSourceAdded(BluetoothDevice rcvr, + byte srcId, + int status) { + + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastAudioSourceAdded: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status == BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + //Show Dialog + if (mGroupOperation) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + R.string.bluetooth_source_added_message, commonMessageListener); + } + if(mBroadcastPinCode != null) { + if (status == BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS + && mScanAssistManager != null) { + mScanAssistManager.setBroadcastCode(srcId,mBroadcastPinCode, mGroupOperation); + } + mBroadcastPinCode = null; + } + finish(); + } else { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + getSourceAdditionErrMessage(status), commonMessageListener); + } + }; + + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastAudioSourceUpdated: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status != BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + getSourceUpdateErrMessage(status), commonMessageListener); + } + }; + + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastPinUpdated: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status != BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + R.string.bluetooth_source_setpin_error_message, commonMessageListener); + } + }; + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + int status) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastAudioSourceRemoved: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status != BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + getSourceRemovalErrMessage(status), commonMessageListener); + } + }; + }; + + + public BluetoothSADetail() { + super(DISALLOW_CONFIG_BLUETOOTH); + } + + void createDevicePreference(CachedBluetoothDevice cachedDevice) { + if (mDeviceListGroup == null) { + Log.w(TAG, "Trying to create a device preference before the list group/category " + + "exists!"); + return; + } + + String key = cachedDevice.getDevice().getAddress(); + BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key); + + if (preference == null) { + preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice, + true/*mShowDevicesWithoutNames*/, BluetoothDevicePreference.SortType.TYPE_FIFO); + preference.setKey(key); + //Set hideSecondTarget is true if it's bonded device. + //preference.hideSecondTarget(true); + mDeviceListGroup.addPreference(preference); + } + + initDevicePreference(preference); + Log.w(TAG, "adding" + cachedDevice + "to the Pref map"); + mDevicePreferenceMap.put(cachedDevice, preference); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mInitialScanStarted = false; + } + + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { + //Do nothing + } + + @Override + public void onStart() { + BroadcastScanAssistanceUtils.debug(TAG, "OnStart Called"); + super.onStart(); + if (mLocalManager == null){ + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + updateContent(mBluetoothAdapter.getState()); + mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering()); + if (mScanAssistManager == null) { + if (mProfileManager == null) { + mProfileManager = mLocalManager.getProfileManager(); + } + BCProfile bcProfile = (BCProfile)mProfileManager.getBCProfile(); + mScanAssistManager = bcProfile.getBSAManager( + mCachedDevice.getDevice(), mScanAssistCallback); + if (mScanAssistManager == null) { + Log.e(TAG, "On Start: not able to instantiate scanAssistManager"); + //return; + } + } + } + + @Override + public void onAttach(Context context) { + BroadcastScanAssistanceUtils.debug(TAG, "OnAttach Called"); + super.onAttach(context); + mContext = context; + String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS); + mGroupOperation = getArguments().getShort(KEY_GROUP_OP) == (short)1; + BluetoothDevice remoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + deviceAddress); + if (mLocalManager == null) { + Log.e(TAG, "Local mgr is NULL"); + mLocalManager = Utils.getLocalBtManager(getActivity()); + if (mLocalManager == null) { + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + } + mCachedDevice = mLocalManager.getCachedDeviceManager().findDevice(remoteDevice); + if (mCachedDevice == null) { + //goBack(); + return; + } else { + mProfileManager = mLocalManager.getProfileManager(); + BCProfile bcProfile = (BCProfile)mProfileManager.getBCProfile(); + mScanAssistManager = bcProfile.getBSAManager( + mCachedDevice.getDevice(), mScanAssistCallback); + if (mScanAssistManager == null) { + Log.e(TAG, "not able to instantiate scanAssistManager"); + //return; + } + } + } + + + @Override + public void onStop() { + super.onStop(); + if (mLocalManager == null){ + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + // Make the device only visible to connected devices. + disableScanning(); + //clear the preference map onStop + mDevicePreferenceMap.clear(); + mScanAssistManager = null; + } + + @Override + void initPreferencesFromPreferenceScreen() { + mScanDelegatorName = findPreference("bt_bcast_rcvr_device"); + mScanDelegatorName.setSelectable(false); + if (mCachedDevice == null) { + mScanDelegatorName.setSummary(SCAN_DEL_NAME); + } else { + mScanDelegatorName.setSummary(getBluetoothName(mCachedDevice.getDevice())); + } + mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_AVAIL_LE_AUDIO_SOURCES); + mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF); + mFooterPreference.setSelectable(false); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.BLUETOOTH_PAIRING; + } + + @Override + void enableScanning() { + // Clear all device states before first scan + if (!mInitialScanStarted) { + if (mAvailableDevicesCategory != null) { + removeAllDevices(); + } + mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); + mInitialScanStarted = true; + } + //Call to Scan for LE Audio Sources + if (mScanAssistManager != null) { + BroadcastScanAssistanceUtils.debug(TAG, "call searchforLeAudioBroadcasters"); + mScanAssistManager.searchforLeAudioBroadcasters(); + } + } + + @Override + void disableScanning() { + if (mScanAssistManager != null && mScanning == true) { + BroadcastScanAssistanceUtils.debug(TAG, "call stopSearchforLeAudioBroadcasters"); + mScanAssistManager.stopSearchforLeAudioBroadcasters(); + mScanning = false; + } + } + + private int getSyncStateFromSelection (String s) { + int ret = -1; + if (s == null) { + BroadcastScanAssistanceUtils.debug(TAG, "getSyncStateFromSelection:Invalid Input"); + } else { + if (mSyncState.equals("Sync Metadata")) { + ret = BleBroadcastAudioScanAssistManager.SYNC_METADATA; + } else { + ret = BleBroadcastAudioScanAssistManager.SYNC_METADATA_AUDIO; + } + } + return ret; + } + + void launchSyncAndBroadcastIndexOptions(List broadcastSourceIndicies) { + Context context = getContext(); + + final View dialogView; + String title, message; + Activity activity = getActivity(); + if (isAdded() && activity != null) { + dialogView = getLayoutInflater().inflate(R.layout.select_source_prompt, null); + String name = null; + if (clickedDevice != null) { + name = clickedDevice.getName(); + } + if (TextUtils.isEmpty(name)) { + name = context.getString(R.string.bluetooth_device); + } + if (mGroupOperation) { + message = context.getString(R.string.bluetooth_grp_source_selection_options_detail, name); + } else { + message = context.getString(R.string.bluetooth_source_selection_options_detail, name); + } + title = context.getString(R.string.bluetooth_source_selection_options_detail_title); + + /* + //BIS Selection choice + ListView bisSelectionList; + bisSelectionList = dialogView.findViewById(R.id.lv); + bisSelectionList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + ArrayAdapter arrayAdapter = + new ArrayAdapter(context, android.R.layout.simple_list_item_multiple_choice , broadcastSourceIndicies); + + bisSelectionList.setAdapter(arrayAdapter); + + bisSelectionList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + BroadcastScanAssistanceUtils.debug(TAG, "onItemClick: " +position); + CheckedTextView v = (CheckedTextView) view; + boolean currentCheck = v.isChecked(); + BleBroadcastSourceChannel bisIndex = (BleBroadcastSourceChannel) bisSelectionList.getItemAtPosition(position); + bisIndex.setStatus(currentCheck); + } + }); + */ + DialogInterface.OnClickListener cancelAddSourceListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + BroadcastScanAssistanceUtils.debug(TAG, ">>Cancel clicked"); + finish(); + } + }; + + DialogInterface.OnClickListener addSourceListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + /* + Radio Buttons + final RadioGroup group = dialogView.findViewById(R.id.syncStateOptions); + int selectedId = group.getCheckedRadioButtonId(); + RadioButton radioSelectedButton = (RadioButton) dialogView.findViewById(selectedId); + mSyncState = radioSelectedButton.getText().toString(); + BroadcastScanAssistanceUtils.debug(TAG, "mSyncState: " + mSyncState); + */ + if (clickedDevice == null) { + Log.w(TAG, "Ignore as there is no clicked device"); + } + if (clickedDevice.getAddress().equals(mBluetoothAdapter.getAddress())) { + BroadcastScanAssistanceUtils.debug(TAG, ">>Local Adapter"); + mBroadcastPinCode = null; + } else { + EditText broadcastPIN = dialogView.findViewById(R.id.broadcastPINcode); + mBroadcastPinCode = broadcastPIN.getText().toString(); + BroadcastScanAssistanceUtils.debug(TAG, "broadcastPinCode: " + mBroadcastPinCode); + if (TextUtils.isEmpty(mBroadcastPinCode)) { + BroadcastScanAssistanceUtils.debug(TAG, "Empty broacast PinCode"); + mBroadcastPinCode = null; + } + } + if (mScanAssistManager != null && clickedDevice != null) { + mScanAssistManager.addBroadcastSource(clickedDevice.getDevice(), + /*getSyncStateFromSelection(mSyncState)*/ + BleBroadcastAudioScanAssistManager.SYNC_METADATA_AUDIO, + broadcastSourceIndicies, mGroupOperation); + } + } + }; + EditText broadcastPIN = dialogView.findViewById(R.id.broadcastPINcode); + if (clickedDevice != null && clickedDevice.getAddress().equals(mBluetoothAdapter.getAddress())) { + BroadcastScanAssistanceUtils.debug(TAG, "Local Adapter"); + mBroadcastPinCode = null; + broadcastPIN.setVisibility(View.INVISIBLE); + if (mGroupOperation) { + message = context.getString(R.string.bluetooth_col_grp_source_selection_options_detail, name); + } else { + message = context.getString(R.string.bluetooth_col_source_selection_options_detail, name); + } + } + mScanAssistDetailsDialog = BroadcastScanAssistanceUtils.showScanAssistDetailsDialog(context, + mScanAssistDetailsDialog, addSourceListener, cancelAddSourceListener, title, + Html.fromHtml(message), dialogView); + } + } + + @Override + void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + disableScanning(); + clickedDevice = btPreference.getBluetoothDevice(); + VendorCachedBluetoothDevice vDevice = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(clickedDevice, mProfileManager); + if (mScanAssistManager != null) { + BroadcastScanAssistanceUtils.debug(TAG, "calling selectAudioSource"); + mScanAssistManager.selectBroadcastSource(vDevice.getScanResult(), mGroupOperation); + } + } + void updateContent(int bluetoothState) { + switch (bluetoothState) { + case BluetoothAdapter.STATE_ON: + mDevicePreferenceMap.clear(); + //mBluetoothAdapter.enable(); + + addDeviceCategory(mAvailableDevicesCategory, + R.string.bluetooth_preference_found_media_devices, + BluetoothDeviceFilter.ALL_FILTER, false); + updateFooterPreference(mFooterPreference); + //mAlwaysDiscoverable.start(); + enableScanning(); + break; + + case BluetoothAdapter.STATE_OFF: + finish(); + break; + } + } + + @Override + void updateFooterPreference(Preference myDevicePreference) { + final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); + myDevicePreference.setTitle(getString( + R.string.bluetooth_footer_mac_message, + bidiFormatter.unicodeWrap(mCachedDevice.getAddress()))); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + super.onBluetoothStateChanged(bluetoothState); + updateContent(bluetoothState); + if (bluetoothState == BluetoothAdapter.STATE_ON) { + showBluetoothTurnedOnToast(); + } + } + + + + @Override + public int getHelpResource() { + return R.string.help_url_bluetooth; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bluetooth_search_bcast_sources; + } + + @Override + public String getDeviceListKey() { + return KEY_AVAIL_LE_AUDIO_SOURCES; + } + + void showBluetoothTurnedOnToast() { + Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast, + Toast.LENGTH_SHORT).show(); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..9883e70ae899f1c5ab7b836c043ac7aeefce20b7 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bluetooth; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.DialogInterface; +import android.provider.Settings; +import android.util.Log; +import android.widget.Toast; +import android.text.InputType; +import android.view.View; + +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; +import android.bluetooth.BluetoothAdapter; + +/** + * BroadcastScanAssistanceUtils is a helper class that contains constants for various + * Android resource IDs, debug logging flags, and static methods + * for creating BASS dialogs. + */ +public final class BroadcastScanAssistanceUtils { + + private static final String TAG = "BroadcastScanAsssitanceBroadcastScanAssistanceUtils"; + static final boolean BASS_DBG = Log.isLoggable(TAG, Log.VERBOSE); + + private BroadcastScanAssistanceUtils() { + } + + static void debug(String TAG, String msg) { + if (BASS_DBG) { + Log.d(TAG, msg); + } + } + + static boolean isLocalDevice(BluetoothDevice dev) { + boolean ret = false; + if (dev != null) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + ret = btAdapter.getAddress().equals(dev.getAddress()); + } + Log.d(TAG, "isLocalBroadcastSource returns" +ret); + return ret; + } + + static AlertDialog showScanAssistError(Context context, String name, int messageResId, + DialogInterface.OnClickListener okListener) { + return showScanAssistError(context, name, messageResId, Utils.getLocalBtManager(context), okListener); + } + + private static AlertDialog showScanAssistError(Context context, String name, int messageResId, + LocalBluetoothManager manager, DialogInterface.OnClickListener okListener) { + String message = context.getString(messageResId, name); + Context activity = manager.getForegroundActivity(); + AlertDialog dialog = null; + if (manager.isForegroundActivity()) { + try { + dialog = new AlertDialog.Builder(activity) + .setTitle(R.string.bluetooth_error_title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, okListener) + .show(); + } catch (Exception e) { + Log.e(TAG, "Cannot show error dialog.", e); + return null; + } + return dialog; + } else { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + return dialog; + } + } + // Create (or recycle existing) and show disconnect dialog. + static AlertDialog showScanAssistDetailsDialog(Context context, + AlertDialog dialog, + DialogInterface.OnClickListener addSourceListener, + DialogInterface.OnClickListener cancelAddSourceListener, + CharSequence title, CharSequence message, + View customView + ) { + if (dialog == null) { + dialog = new AlertDialog.Builder(context) + .setPositiveButton(android.R.string.ok, addSourceListener) + .setNegativeButton(android.R.string.cancel, cancelAddSourceListener) + .setView(customView) + .create(); + } else { + if (dialog.isShowing()) { + dialog.dismiss(); + } + // use disconnectListener for the correct profile(s) + //CharSequence okText = context.getText(android.R.string.ok); + //dialog.setButton(DialogInterface.BUTTON_POSITIVE, + // okText, addSourceListener); + } + dialog.setTitle(title); + dialog.setMessage(message); + dialog.show(); + return dialog; + } + + static AlertDialog showAssistanceGroupOptionsDialog(Context context, + AlertDialog dialog, + DialogInterface.OnClickListener groupOpListener, + DialogInterface.OnClickListener singleDevListener, + CharSequence title, CharSequence message) { + if (dialog == null) { + Log.d(TAG, "showAssistanceGroupOptionsDialog creation"); + dialog = new AlertDialog.Builder(context) + .setPositiveButton(R.string.yes, groupOpListener) + .setNegativeButton(R.string.no, singleDevListener) + .create(); + } else { + if (dialog.isShowing()) { + dialog.dismiss(); + } + // use disconnectListener for the correct profile(s) + //CharSequence okText = context.getText(android.R.string.yes); + //dialog.setButton(DialogInterface.BUTTON_POSITIVE, + // okText, groupOpListener); + } + dialog.setTitle(title); + dialog.setMessage(message); + dialog.show(); + return dialog; + } +} diff --git a/le_audio/system/bt/binder/Android.bp b/le_audio/system/bt/binder/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..f246cbfed4836ac7cdab9c4e6e579ba8529b875f --- /dev/null +++ b/le_audio/system/bt/binder/Android.bp @@ -0,0 +1,28 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +// AIDL interface between libbluetooth-binder and framework.jar +filegroup { + name: "libbluetooth-binder-aidl-adva", + srcs: [ + "android/bluetooth/IBluetoothSyncHelper.aidl", + "android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl", + "android/bluetooth/IBluetoothBroadcast.aidl", + ], +} + diff --git a/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl new file mode 100644 index 0000000000000000000000000000000000000000..4b94f9493c032681bb29eb6de802e95b08473c6c --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl @@ -0,0 +1,23 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package android.bluetooth; + +parcelable BleBroadcastSourceChannel; + diff --git a/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..8eb14da6201d92eaf29a4b5a9df775b53a9091f2 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl @@ -0,0 +1,22 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package android.bluetooth; + +parcelable BleBroadcastSourceInfo; + diff --git a/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl b/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..223f8135fa58c97af5e7f6a0f7d4471adf4bf7f9 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl @@ -0,0 +1,48 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.le.ScanResult; + +/** @hide */ +interface IBleBroadcastAudioScanAssistCallback { + void onBleBroadcastSourceFound(in ScanResult scanres); + void onBleBroadcastAudioSourceSelected(in BluetoothDevice device, + in int status, + in List + broadcastSourceChannels); + + void onBleBroadcastAudioSourceAdded(in BluetoothDevice rcvr, + in byte srcId, + in int status); + void onBleBroadcastAudioSourceUpdated(in BluetoothDevice rcvr, + in byte srcId, + in int status); + + void onBleBroadcastPinUpdated(in BluetoothDevice rcvr, + in byte srcId, + in int status); + void onBleBroadcastAudioSourceRemoved(in BluetoothDevice rcvr, + in byte srcId, + in int status); +} diff --git a/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl b/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl new file mode 100644 index 0000000000000000000000000000000000000000..e3054e9aaee76f4e0d03666b626161d56e23775b --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl @@ -0,0 +1,35 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * APIs for Bluetooth Broadcast service + * + * @hide + */ +interface IBluetoothBroadcast { + // Public API + boolean SetBroadcast(in boolean enable, in String packageName); + boolean SetEncryption(in boolean enable, in int enc_len, + in boolean use_existing, in String packageName); + byte[] GetEncryptionKey(in String packageName); + int GetBroadcastStatus(in String packageName); +} diff --git a/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl b/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl new file mode 100644 index 0000000000000000000000000000000000000000..495c1ffb017145e59ebe6e54d3821735d8516c48 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl @@ -0,0 +1,75 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.le.ScanResult; + +/** + * APIs for Bluetooth Bluetooth Scan offloader service + * + * @hide + */ +interface IBluetoothSyncHelper { + // Public API + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + List getConnectedDevices(); + List getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setConnectionPolicy(in BluetoothDevice device, int connectionPolicy); + int getConnectionPolicy(in BluetoothDevice device); + boolean startScanOffload (in BluetoothDevice device, + in boolean groupOp); + boolean stopScanOffload (in BluetoothDevice device, + in boolean groupOp); + + void registerAppCallback(in BluetoothDevice device, + in IBleBroadcastAudioScanAssistCallback cb); + void unregisterAppCallback(in BluetoothDevice device, + in IBleBroadcastAudioScanAssistCallback cb); + + boolean searchforLeAudioBroadcasters (in BluetoothDevice device); + boolean stopSearchforLeAudioBroadcasters(in BluetoothDevice device); + + boolean addBroadcastSource(in BluetoothDevice device, + in BleBroadcastSourceInfo srcInfo, + in boolean groupOp + ); + boolean selectBroadcastSource(in BluetoothDevice device, + in ScanResult scanRes, + in boolean groupOp + ); + boolean updateBroadcastSource(in BluetoothDevice device, + in BleBroadcastSourceInfo srcInfo, + in boolean groupOp + ); + boolean setBroadcastCode (in BluetoothDevice device, + in BleBroadcastSourceInfo srcInfo, + in boolean groupOp + ); + boolean removeBroadcastSource (in BluetoothDevice device, + in byte SourceId, + in boolean groupOp + ); + List getAllBroadcastSourceInformation( + in BluetoothDevice device); +} diff --git a/le_audio/system/bt/bta/Android.bp b/le_audio/system/bt/bta/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..c90b78836fb9bf28b862a160a303ac0f680279db --- /dev/null +++ b/le_audio/system/bt/bta/Android.bp @@ -0,0 +1,88 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +cc_defaults { + name: "fluoride_bta_defaults_qti_adva", + defaults: ["fluoride_defaults"], + local_include_dirs: [ + "include", + ], + include_dirs: [ + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/ag", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btcore/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/hci/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/internal_include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/btm", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/udrv/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/vnd/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/utils/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext", + "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/btif/include", + "vendor/qcom/opensource/commonsys-intf/bluetooth/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/device/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btif/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/sys", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/bta/bap", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/btif/include", + "packages/modules/Bluetooth/system/common/" + ], + shared_libs: [ + "libcutils", + ], + header_libs: ["libbluetooth_headers"], + cflags: [ + "-DBUILDCFG", + "-DADV_AUDIO_FEATURE=1", + ], +} + +// BTA static library for target +// ======================================================== +cc_library_static { + name: "libbt-bta_qti_adva", + defaults: ["fluoride_bta_defaults_qti_adva"], + enabled: false, + srcs: [ + "csip/bta_csip_act.cc", + "csip/bta_csip_api.cc", + "csip/bta_csip_main.cc", + "csip/bta_csip_utils.cc", + "bap/ascs_client.cc", + "bap/pacs_client.cc", + "bap/gattc_ops_queue.cc", + "bap/gatts_ops_queue.cc", + "bap/uclient_main.cc", + "bap/uclient_strm_mgr.cc", + "bap/uclient_strm_tracker.cc", + "bap/connected_iso.cc", + "bap/uclient_alarm.cc", + "vcp/bta_vcp_controller.cc", + "dm/bta_dm_adv_audio.cc", + "mcp/bta_mcp_main.cc", + "cc/bta_cc_main.cc", + ], +} diff --git a/le_audio/system/bt/bta/bap/ascs_client.cc b/le_audio/system/bt/bta/bap/ascs_client.cc new file mode 100644 index 0000000000000000000000000000000000000000..1be5f7cc26387b5d03545b231f0a3ceeb00a0adf --- /dev/null +++ b/le_audio/system/bt/bta/bap/ascs_client.cc @@ -0,0 +1,1731 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bta_gatt_api.h" +#include "bta_ascs_client_api.h" +#include "gattc_ops_queue.h" +#include +#include +#include +#include +#include "stack/btm/btm_int.h" +#include "device/include/controller.h" + +#include +#include "btif/include/btif_bap_config.h" +#include "osi/include/log.h" +#include "btif_util.h" + +namespace bluetooth { +namespace bap { +namespace ascs { + +using base::Closure; +using bluetooth::bap::GattOpsQueue; + +Uuid ASCS_UUID = Uuid::FromString("184E"); +Uuid ASCS_SINK_ASE_UUID = Uuid::FromString("2BC4"); +Uuid ASCS_SRC_ASE_UUID = Uuid::FromString("2BC5"); +Uuid ASCS_ASE_CP_UUID = Uuid::FromString("2BC6"); + +class AscsClientImpl; +AscsClientImpl* instance; + +typedef uint8_t codec_type_t[5]; + +enum class ProfleOP { + CONNECT, + DISCONNECT +}; + +struct ProfileOperation { + uint16_t client_id; + ProfleOP type; +}; + +enum class DevState { + IDLE = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +void ascs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +void encryption_callback(const RawAddress*, tGATT_TRANSPORT, void*, + tBTM_STATUS); + +std::map resp_codes = { + {0x01, "Un Supported Opcode"}, + {0x02, "Invalid Length"}, + {0x03, "Invalid ASE ID"}, + {0x04, "Invalid ASE SM Transition"}, + {0x05, "Invalid ASE Direction"}, + {0x06, "Un Supported Audio Capabilities"}, + {0x07, "Un Supported Config Param"}, + {0x08, "Rejected Config Param"}, + {0x09, "Invalid Config Param"}, + {0x0A, "Un Supported Metadata"}, + {0x0B, "Rejected Metadata"}, + {0x0C, "Invalid Metadata"}, + {0x0D, "InSufficient Resources"}, + {0x0E, "Unspecified Error"}, +}; + +std::map reason_codes = { + {0x01, "Codec ID"}, + {0x02, "Codec Specific Config"}, + {0x03, "SDU Interval"}, + {0x04, "Framing"}, + {0x05, "PHY"}, + {0x06, "Maximum SDU Size"}, + {0x07, "RTN"}, + {0x08, "MTL"}, + {0x09, "PD"}, + {0x0A, "Invalid ASE CIS Mapping"}, +}; + +std::vector sink_ase_value_list, src_ase_value_list; +AseParams ase; + +struct AscsDevice { + RawAddress address; + /* This is true only during first connection to profile, until we store the + * device */ + bool first_connection; + bool service_changed_rcvd; + + uint16_t conn_id; + std::vector sink_ase_list; + std::vector src_ase_list; + uint16_t ase_cp_handle; + uint16_t ase_cp_ccc_handle; + uint16_t srv_changed_ccc_handle; + bool discovery_completed; + uint8_t num_ases_read; + bool notifications_enabled; + DevState state; + bool is_congested; + std::vector profile_queue; + std::vector connected_client_list; //list client requested for connection + AscsDevice(const RawAddress& address) : address(address) {} +}; + +class AscsDevices { + public: + void Add(AscsDevice device) { + if (FindByAddress(device.address) != nullptr) return; + + devices.push_back(device); + } + + void Remove(const RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->address != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + AscsDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const AscsDevice& device) { + return device.address == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + AscsDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const AscsDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector devices; +}; + +class AscsClientImpl : public AscsClient { + public: + ~AscsClientImpl() override = default; + + AscsClientImpl() : gatt_client_id(BTA_GATTS_INVALID_IF) {}; + + bool Register(AscsClientCallbacks *callback) { + LOG(WARNING) << __func__ << callback; + // looks for client is already registered + bool is_client_registered = false; + for (auto it : callbacks) { + AscsClientCallbacks *pac_callback = it.second; + if(callback == pac_callback) { + is_client_registered = true; + break; + } + } + + LOG(WARNING) << __func__ ; + + if(is_client_registered) { + LOG(WARNING) << __func__ << " already registered"; + return false; + } + + if(gatt_client_id == BTA_GATTS_INVALID_IF) { + BTA_GATTC_AppRegister( + ascs_gattc_callback, + base::Bind( + [](AscsClientCallbacks *callback, uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start ASCS profile - no gatt " + "clients left!"; + return; + } + + if (instance) { + LOG(WARNING) << " ASCS gatt_client_id " + << instance->gatt_client_id; + instance->gatt_client_id = client_id; + instance->callbacks.insert(std::make_pair( + ++instance->ascs_client_id, callback)); + callback->OnAscsInitialized(0, instance->ascs_client_id); + } + }, + callback), true); + } else { + instance->callbacks.insert(std::make_pair( + ++instance->ascs_client_id, callback)); + callback->OnAscsInitialized(0, instance->ascs_client_id); + } + return true; + } + + bool Deregister (uint16_t client_id) { + bool status = false; + auto it = callbacks.find(client_id); + if (it != callbacks.end()) { + callbacks.erase(it); + if(callbacks.empty()) { + // deregister with GATT + LOG(WARNING) << __func__ << " Gatt de-register from ascs"; + BTA_GATTC_AppDeregister(gatt_client_id); + gatt_client_id = BTA_GATTS_INVALID_IF; + } + status = true; + } + return status; + } + + uint8_t GetClientCount () { + return callbacks.size(); + } + + void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) override { + LOG(WARNING) << __func__ << " " << address; + AscsDevice *dev = ascsDevices.FindByAddress(address); + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::CONNECT; + + if(dev == nullptr) { + AscsDevice pac_dev(address); + ascsDevices.Add(pac_dev); + dev = ascsDevices.FindByAddress(address); + } + if (dev == nullptr) { + LOG(ERROR) << __func__ << "dev is null"; + return; + } + + switch(dev->state) { + case DevState::IDLE: { + BTA_GATTC_Open(gatt_client_id, address, is_direct, + GATT_TRANSPORT_LE, false); + dev->state = DevState::CONNECTING; + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTING: { + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id](uint16_t id) { + return id == client_id; + }); + + if(iter == dev->connected_client_list.end()) + dev->connected_client_list.push_back(client_id); + + auto it = callbacks.find(client_id); + if (it != callbacks.end()) { + AscsClientCallbacks *callback = it->second; + callback->OnConnectionState(address, GattState::CONNECTED); + } + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + } + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + LOG(WARNING) << __func__ << " " << address; + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::DISCONNECT; + + switch(dev->state) { + case DevState::CONNECTING: { + auto iter = std::find_if(dev->profile_queue.begin(), + dev->profile_queue.end(), + [&client_id]( ProfileOperation entry) { + return ((entry.type == ProfleOP::CONNECT) && + (entry.client_id == client_id)); + }); + // If it is the last client requested for disconnect + if(iter != dev->profile_queue.end() && + dev->profile_queue.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->profile_queue.push_back(op); + dev->state = DevState::DISCONNECTING; + } else { + // clear the connection queue and + // move the state to DISCONNECTING to better track + dev->profile_queue.clear(); + dev->state = DevState::DISCONNECTING; + dev->profile_queue.push_back(op); + } + } else { + // remove the connection entry from the list + // as the same client has requested for disconnection + dev->profile_queue.erase(iter); + } + } break; + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // if it is the last client requested for disconnection + if(iter != dev->connected_client_list.end() && + dev->connected_client_list.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->profile_queue.push_back(op); + dev->state = DevState::DISCONNECTING; + } + } else { + // remove the client from connected_client_list + dev->connected_client_list.erase(iter); + // remove the pending gatt ops( not the ongoing one ) + // initiated from client which requested disconnect + // TODO and send callback as disconnected + } + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + default: + break; + } + } + + void StartDiscovery(uint16_t client_id, const RawAddress& address) override { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(WARNING) << __func__ << " " << address; + + switch(dev->state) { + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + LOG(WARNING) << __func__ << client_id << stored_client_id; + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if(iter == dev->connected_client_list.end()) { + break; + } + // check if the discovery is already finished + // send back the same results to the other client + if(dev->discovery_completed && dev->notifications_enabled) { + sink_ase_value_list.clear(); + src_ase_value_list.clear(); + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + for (auto it : dev->sink_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + sink_ase_value_list.push_back(ase); + } + for (auto it : dev->src_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + src_ase_value_list.push_back(ase); + } + + AscsClientCallbacks *callback = iter->second; + // send out the callback as service discovery completed + callback->OnSearchComplete(0, dev->address, + sink_ase_value_list, + src_ase_value_list); + } + break; + } + // reset it + dev->num_ases_read = 0x00; + dev->discovery_completed = false; + dev->notifications_enabled = false; + // queue the request to GATT queue module + GattOpsQueue::ServiceSearch(client_id, dev->conn_id, &ASCS_UUID); + } break; + default: + break; + } + } + + void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::CODEC_CONFIG); + uint8_t num_ases = codec_configs.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = codec_configs.begin(); + while (it != codec_configs.end()) { + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + vect_val.insert(vect_val.end(), &it->tgt_latency, &it->tgt_latency + 1); + vect_val.insert(vect_val.end(), &it->tgt_phy, &it->tgt_phy + 1); + vect_val.insert(vect_val.end(), it->codec_id, + ((uint8_t *)it->codec_id) + sizeof(codec_type_t)); + + vect_val.insert(vect_val.end(), &it->codec_params_len, + &it->codec_params_len + 1); + vect_val.insert(vect_val.end(), it->codec_params.begin(), + it->codec_params.end()); + + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + LOG(INFO) << ": Target Latency = " << loghex(it->tgt_latency); + LOG(INFO) << ": target Phy = " << loghex(it->tgt_phy); + LOG(INFO) << ": Codec ID = " << loghex(it->codec_id[0]); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::QOS_CONFIG); + uint8_t num_ases = qos_configs.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = qos_configs.begin(); + while (it != qos_configs.end()) { + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + vect_val.insert(vect_val.end(), &it->cig_id, &it->cig_id + 1); + vect_val.insert(vect_val.end(), &it->cis_id, &it->cis_id + 1); + + vect_val.insert(vect_val.end(), it->sdu_interval, + (uint8_t *)it->sdu_interval + sizeof(sdu_interval_t)); + + // test change it->framing = 0xFF; + vect_val.insert(vect_val.end(), &it->framing, &it->framing + 1); + vect_val.insert(vect_val.end(), &it->phy, &it->phy + 1); + + vect_val.insert(vect_val.end(), (uint8_t *) &it->max_sdu_size, + (uint8_t *)&it->max_sdu_size + sizeof(uint16_t)); + + vect_val.insert(vect_val.end(), &it->retrans_number, + &it->retrans_number + 1); + + vect_val.insert(vect_val.end(), (uint8_t *) &it->trans_latency, + (uint8_t *)&it->trans_latency + sizeof(uint16_t)); + + vect_val.insert(vect_val.end(), it->present_delay, + (uint8_t *)it->present_delay + sizeof(presentation_delay_t)); + + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + LOG(INFO) << ": Cig Id = " << loghex(it->cig_id); + LOG(INFO) << ": Cis Id = " << loghex(it->cis_id); + LOG(INFO) << ": SDU interval =" + << " " << loghex(it->sdu_interval[0]) + << " " << loghex(it->sdu_interval[1]) + << " " << loghex(it->sdu_interval[2]); + LOG(INFO) << ": Framing = " << loghex(it->framing); + LOG(INFO) << ": Phy = " << loghex(it->phy); + LOG(INFO) << ": Max SDU size = " << loghex(it->max_sdu_size); + LOG(INFO) << ": RTN = " << loghex(it->retrans_number); + LOG(INFO) << ": MTL = " << loghex(it->trans_latency); + LOG(INFO) << ": PD =" + << " " << loghex(it->present_delay[0]) + << " " << loghex(it->present_delay[1]) + << " " << loghex(it->present_delay[2]); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::ENABLE); + uint8_t num_ases = enable_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = enable_ops.begin(); + while (it != enable_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + // test change it->meta_data_len = 0xFF; + vect_val.insert(vect_val.end(), &it->meta_data_len, + &it->meta_data_len + 1); + vect_val.insert(vect_val.end(), it->meta_data.begin(), + it->meta_data.end()); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::START_READY); + uint8_t num_ases = start_ready_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = start_ready_ops.begin(); + while (it != start_ready_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::DISABLE); + uint8_t num_ases = disable_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = disable_ops.begin(); + while (it != disable_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::STOP_READY); + uint8_t num_ases = stop_ready_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = stop_ready_ops.begin(); + while (it != stop_ready_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::RELEASE); + uint8_t num_ases = release_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = release_ops.begin(); + while (it != release_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::UPDATE_META_DATA); + uint8_t num_ases = metadata_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = metadata_ops.begin(); + while (it != metadata_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + vect_val.insert(vect_val.end(), &it->meta_data_len, + &it->meta_data_len + 1); + vect_val.insert(vect_val.end(), it->meta_data.begin(), + it->meta_data.end()); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + bool GetAseParams(const RawAddress& address, uint8_t ase_id, + AseParams *ase_params) { + bool ase_found = false; + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return false; + } + + // first look for sink ASEs + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_params = it->ase_params; + ase_found = true; + break; + } + } + if(ase_found) return ase_found; + + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_params = it->ase_params; + ase_found = true; + break; + } + } + return ase_found; + } + + bool GetAseHandle(const RawAddress& address, uint8_t ase_id, + uint16_t *ase_handle) { + bool ase_found = false; + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return false; + } + + // first look for sink ASEs + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_handle = it->ase_handle; + ase_found = true; + break; + } + } + if(ase_found) return ase_found; + + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_handle = it->ase_handle; + ase_found = true; + break; + } + } + return ase_found; + } + + void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(WARNING) << __func__ << " " << address; + + switch(dev->state) { + case DevState::CONNECTED: { + uint16_t ase_handle; + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if(iter == dev->connected_client_list.end()) { + break; + } + + // check if the discovery is already finished + // send back the same results to the other client + if(dev->discovery_completed && dev->notifications_enabled) { + auto iter = callbacks.find(client_id); + AseParams ase_params; + if(iter != callbacks.end() && + GetAseParams(address, ase_id, &ase_params)) { + AscsClientCallbacks *callback = iter->second; + callback->OnAseState(dev->address, ase_params); + } + break; + } + + if(GetAseHandle(address, ase_id, &ase_handle)) { + // queue the request to GATT queue module + GattOpsQueue::ReadCharacteristic(client_id, dev->conn_id, + ase_handle, + AscsClientImpl::OnReadAseStateStatic, nullptr); + } + } break; + default: + LOG(WARNING) << __func__ << "un-handled event"; + break; + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBTA_TRANSPORT transport, uint16_t mtu) { + + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + /* When device is quickly disabled and enabled in settings, this case + * might happen */ + LOG(ERROR) << "Closing connection to non ascs device, address=" + << address; + BTA_GATTC_Close(conn_id); + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Status : " << loghex(status); + + if(dev->state == DevState::CONNECTING) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Failed to connect to ASCS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + ascsDevices.Remove(address); + return; + } + } else if(dev->state == DevState::DISCONNECTING) { + // TODO will this happens ? + // it could have called the cancel open to expect the + // open cancelled event + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Failed to connect to ASCS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + ascsDevices.Remove(address); + return; + } else { + // gatt connected successfully + // if the disconnect entry is found we need to initiate the + // gatt disconnect. may be a race condition just after sending + // cancel open gatt connected event received + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + break; + } + } else { + it++; + } + } + return; + } + } else { + // return unconditinally + return; + } + + // success scenario code + dev->conn_id = conn_id; + + tACL_CONN* p_acl = btm_bda_to_acl(address, BT_TRANSPORT_LE); + if (p_acl != nullptr && + controller_get_interface()->supports_ble_2m_phy() && + HCI_LE_2M_PHY_SUPPORTED(p_acl->peer_le_features)) { + LOG(INFO) << address << " set preferred PHY to 2M"; + BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0); + } + + /* verify bond */ + uint8_t sec_flag = 0; + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + /* if link has been encrypted */ + OnEncryptionComplete(address, true); + return; + } + + if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + /* if bonded and link not encrypted */ + sec_flag = BTM_BLE_SEC_ENCRYPT; + LOG(WARNING) << "trying to encrypt now"; + BTM_SetEncryption(address, BTA_TRANSPORT_LE, encryption_callback, nullptr, + sec_flag); + return; + } + + /* otherwise let it go through */ + OnEncryptionComplete(address, true); + } + + void OnGattDisconnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress remote_bda, + tBTA_GATT_REASON reason) { + AscsDevice* dev = ascsDevices.FindByAddress(remote_bda); + if (!dev) { + LOG(ERROR) << "Skipping unknown device disconnect, conn_id=" + << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << remote_bda + << ", Status : " << loghex(status) + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTING: { + // sudden disconnection + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, GattState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + } break; + case DevState::CONNECTED: { + // sudden disconnection + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + auto iter = callbacks.find(*it); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, GattState::DISCONNECTED); + } + it = dev->connected_client_list.erase(it); + } + } break; + case DevState::DISCONNECTING: { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, GattState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + it = dev->connected_client_list.erase(it); + } + // check if the connection queue is not empty + // if not initiate the Gatt connection + } break; + default: + break; + } + + if (dev->conn_id) { + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->conn_id = 0; + } + + dev->state = DevState::IDLE; + ascsDevices.Remove(remote_bda); + } + + void OnConnectionUpdateComplete(uint16_t conn_id, tBTA_GATTC* p_data) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + } + + void OnEncryptionComplete(const RawAddress& address, bool success) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << "Skipping unknown device" << address; + return; + } + + if(dev->state != DevState::CONNECTING) { + LOG(ERROR) << "received in wrong state" << address; + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Status : " << loghex(success); + + // encryption failed + if (!success) { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::DISCONNECTED); + } + // change the type to disconnect + it->type = ProfleOP::DISCONNECT; + } else { + it++; + } + } + dev->state = DevState::DISCONNECTING; + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + BTA_GATTC_Close(dev->conn_id); + } else { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + dev->connected_client_list.push_back(it->client_id); + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::CONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::CONNECTED; + } + } + + void OnServiceChangeEvent(const RawAddress& address) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << "Skipping unknown device" << address; + return; + } + LOG(INFO) << __func__ << ": address=" << address; + dev->first_connection = true; + dev->service_changed_rcvd = true; + GattOpsQueue::Clean(dev->conn_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << "Skipping unknown device" << address; + return; + } + if (dev->service_changed_rcvd) { + // queue the request to GATT queue module with dummu client id + GattOpsQueue::ServiceSearch(0XFF, dev->conn_id, &ASCS_UUID); + } + } + + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << dev->address + << ": Status : " << loghex(status); + + uint16_t client_id = GattOpsQueue::ServiceSearchComplete(conn_id, + status); + auto iter = callbacks.find(client_id); + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << "Service discovery failed"; + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + std::vector ase_value_list; + callback->OnSearchComplete(0xFF, dev->address, ase_value_list, + ase_value_list); + } + return; + } + + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* service = nullptr; + if (services) { + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) { + LOG(INFO) << "Found UUID_SERVCLASS_GATT_SERVER, handle=" + << loghex(tmp.handle); + const gatt::Service* service_changed_service = &tmp; + find_server_changed_ccc_handle(conn_id, service_changed_service); + } else if (tmp.uuid == ASCS_UUID) { + LOG(INFO) << "Found ASCS service, handle=" << loghex(tmp.handle); + service = &tmp; + } + } + } else { + LOG(ERROR) << "no services found for conn_id: " << conn_id; + return; + } + + if (!service) { + LOG(ERROR) << "No ASCS service found"; + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + std::vector ase_value_list; + callback->OnSearchComplete(0xFF, dev->address, ase_value_list, + ase_value_list); + } + return; + } + + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == ASCS_SINK_ASE_UUID || + charac.uuid == ASCS_SRC_ASE_UUID) { + Ase ase_info; + ase_info.ase_handle = charac.value_handle; + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + AscsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + ase_info.ase_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + + if(charac.uuid == ASCS_SINK_ASE_UUID) { + dev->sink_ase_list.push_back(ase_info); + } else if(charac.uuid == ASCS_SRC_ASE_UUID) { + dev->src_ase_list.push_back(ase_info); + } + if(ase_info.ase_ccc_handle) { + /* Register and enable the Audio Status Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + conn_id, dev->address, ase_info.ase_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + } + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor( + client_id, conn_id, ase_info.ase_ccc_handle, + std::move(value), GATT_WRITE, nullptr, nullptr); + } + } else if (charac.uuid == ASCS_ASE_CP_UUID) { + dev->ase_cp_handle = charac.value_handle; + + dev->ase_cp_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + if(dev->ase_cp_ccc_handle) { + /* Register and enable the Audio Status Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + conn_id, dev->address, dev->ase_cp_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + } + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor( + client_id, conn_id, dev->ase_cp_ccc_handle, + std::move(value), GATT_WRITE, nullptr, nullptr); + } + } else { + LOG(WARNING) << "Unknown characteristic found:" << charac.uuid; + } + } + + dev->notifications_enabled = true; + + if (dev->service_changed_rcvd) { + dev->service_changed_rcvd = false; + } + } + + const char* GetAseState(uint8_t event) { + switch (event) { + CASE_RETURN_STR(ASE_STATE_IDLE) + CASE_RETURN_STR(ASE_STATE_CODEC_CONFIGURED) + CASE_RETURN_STR(ASE_STATE_QOS_CONFIGURED) + CASE_RETURN_STR(ASE_STATE_ENABLING) + CASE_RETURN_STR(ASE_STATE_STREAMING) + CASE_RETURN_STR(ASE_STATE_DISABLING) + CASE_RETURN_STR(ASE_STATE_RELEASING) + default: + return "Unknown State"; + } + } + + const char* GetAseDirection(uint8_t event) { + switch (event) { + CASE_RETURN_STR(ASE_DIRECTION_SINK) + CASE_RETURN_STR(ASE_DIRECTION_SOURCE) + default: + return "Unknown Direction"; + } + } + + void ParseAseParams(uint8_t *p, AseParams *ase_params, uint8_t ase_dir) { + STREAM_TO_UINT8(ase_params->ase_id, p); + STREAM_TO_UINT8(ase_params->ase_state, p); + LOG(INFO) << __func__ + << ": ASE Id = " << loghex(ase_params->ase_id) + << ": ASE State = " << GetAseState(ase_params->ase_state) + << ": ASE Direction = " << GetAseDirection(ase_dir); + switch(ase_params->ase_state) { + case ASE_STATE_CODEC_CONFIGURED: { + AseCodecConfigParams *codec_config = + &ase_params->codec_config_params; + STREAM_TO_UINT8(codec_config->framing, p); + STREAM_TO_UINT8(codec_config->pref_phy, p); + + STREAM_TO_UINT8(codec_config->pref_rtn, p); + STREAM_TO_UINT16(codec_config->mtl, p); + STREAM_TO_ARRAY(&(codec_config->pd_min), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->pd_max), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->pref_pd_min), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->pref_pd_max), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->codec_id), + p, static_cast (sizeof(codec_type_t))); + STREAM_TO_UINT8(codec_config->codec_params_len, p); + if(codec_config->codec_params_len) { + codec_config->codec_params.resize(codec_config->codec_params_len); + STREAM_TO_ARRAY(codec_config->codec_params.data(), + p, codec_config->codec_params_len); + } + LOG(INFO) << ": Framing = " << loghex(codec_config->framing); + LOG(INFO) << ": Pref Phy = " << loghex(codec_config->pref_phy); + LOG(INFO) << ": Pref RTN = " << loghex(codec_config->pref_rtn); + LOG(INFO) << ": MTL = " << loghex(codec_config->mtl); + LOG(INFO) << ": PD Min =" + << " " << loghex(codec_config->pd_min[0]) + << " " << loghex(codec_config->pd_min[1]) + << " " << loghex(codec_config->pd_min[2]); + LOG(INFO) << ": PD Max =" + << " " << loghex(codec_config->pd_max[0]) + << " " << loghex(codec_config->pd_max[1]) + << " " << loghex(codec_config->pd_max[2]); + LOG(INFO) << ": Pref PD Min =" + << " " << loghex(codec_config->pref_pd_min[0]) + << " " << loghex(codec_config->pref_pd_min[1]) + << " " << loghex(codec_config->pref_pd_min[2]); + LOG(INFO) << ": Pref PD Max =" + << " " << loghex(codec_config->pref_pd_max[0]) + << " " << loghex(codec_config->pref_pd_max[1]) + << " " << loghex(codec_config->pref_pd_max[2]); + + LOG(INFO) << ": Codec ID = " << loghex(codec_config->codec_id[0]); + } break; + case ASE_STATE_QOS_CONFIGURED: { + AseQosConfigParams *qos_config = &ase_params->qos_config_params; + STREAM_TO_UINT8(qos_config->cig_id, p); + STREAM_TO_UINT8(qos_config->cis_id, p); + STREAM_TO_ARRAY(&(qos_config->sdu_interval), p, + static_cast (sizeof(sdu_interval_t))); + STREAM_TO_UINT8(qos_config->framing, p); + STREAM_TO_UINT8(qos_config->phy, p); + STREAM_TO_UINT16(qos_config->max_sdu_size, p); + STREAM_TO_UINT8(qos_config->rtn, p); + STREAM_TO_UINT16(qos_config->mtl, p); + STREAM_TO_ARRAY(&(qos_config->pd), p, + static_cast (sizeof(presentation_delay_t))); + + LOG(INFO) << ": Cig Id = " << loghex(qos_config->cig_id); + LOG(INFO) << ": Cis Id = " << loghex(qos_config->cis_id); + LOG(INFO) << ": SDU interval =" + << " " << loghex(qos_config->sdu_interval[0]) + << " " << loghex(qos_config->sdu_interval[1]) + << " " << loghex(qos_config->sdu_interval[2]); + LOG(INFO) << ": Framing = " << loghex(qos_config->framing); + LOG(INFO) << ": Phy = " << loghex(qos_config->phy); + LOG(INFO) << ": Max SDU size = " << loghex(qos_config->max_sdu_size); + LOG(INFO) << ": RTN = " << loghex(qos_config->rtn); + LOG(INFO) << ": MTL = " << loghex(qos_config->mtl); + LOG(INFO) << ": PD =" + << " " << loghex(qos_config->pd[0]) + << " " << loghex(qos_config->pd[1]) + << " " << loghex(qos_config->pd[2]); + } break; + case ASE_STATE_ENABLING: + case ASE_STATE_STREAMING: + case ASE_STATE_DISABLING: { + AseGenericParams *gen_params = &ase_params->generic_params; + STREAM_TO_UINT8(gen_params->cig_id, p); + STREAM_TO_UINT8(gen_params->cis_id, p); + STREAM_TO_UINT8(gen_params->meta_data_len, p); + if(gen_params->meta_data_len) { + gen_params->meta_data.resize(gen_params->meta_data_len); + STREAM_TO_ARRAY(gen_params->meta_data.data(), + p, gen_params->meta_data_len); + } + LOG(INFO) << ": Cig Id = " << loghex(gen_params->cig_id); + LOG(INFO) << ": Cis Id = " << loghex(gen_params->cis_id); + } break; + } + } + + void ParseAseNotification(uint16_t conn_id, + uint16_t handle, uint16_t len, uint8_t* value ) { + uint8_t *p = value; + bool ase_found = false; + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_handle == handle) { + LOG(INFO) << __func__ << ": BD Addr : " << dev->address; + ParseAseParams(p, &it->ase_params, ASE_DIRECTION_SINK); + for (auto iter : callbacks) { + AscsClientCallbacks *ascs_callback = iter.second; + ascs_callback->OnAseState(dev->address, it->ase_params); + } + ase_found = true; + break; + } + } + if(!ase_found) { + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_handle == handle) { + LOG(INFO) << __func__ << ": BD Addr : " << dev->address; + ParseAseParams(p, &it->ase_params,ASE_DIRECTION_SOURCE); + for (auto iter : callbacks) { + AscsClientCallbacks *ascs_callback = iter.second; + ascs_callback->OnAseState(dev->address, it->ase_params); + } + ase_found = true; + break; + } + } + } + } + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + uint8_t* p = value; + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + // check if the notification is for ASEs + if( dev->ase_cp_handle == handle) { // control point notification + AseCpNotification cp_notification; + STREAM_TO_UINT8(cp_notification.ase_opcode, p); + STREAM_TO_UINT8(cp_notification.num_ases, p); + uint8_t num_ases = cp_notification.num_ases; + std::vector ase_cp_notify_list; + AseOpStatus status; + bool notify = false; + + while(num_ases--) { + STREAM_TO_UINT8(status.ase_id, p); + STREAM_TO_UINT8(status.resp_code, p); + STREAM_TO_UINT8(status.reason, p); + if(status.resp_code) { + LOG(ERROR) << __func__ + << ": ASE Id = " << loghex(status.ase_id) + << ": Resp code = " << resp_codes[status.resp_code]; + if(status.reason) { + LOG(ERROR) << ": Reason = " << reason_codes[status.reason]; + } + notify = true; + } + + ase_cp_notify_list.push_back(status); + } + if(notify) { + for (auto iter : callbacks) { + AscsClientCallbacks *ascs_callback = iter.second; + LOG(ERROR) << __func__ << " ASE Operation failed"; + ascs_callback->OnAseOpFailed(dev->address, + (AseOpId) cp_notification.ase_opcode, + ase_cp_notify_list); + } + } + } else { + ParseAseNotification(conn_id, handle, len, value); + } + } + + + void OnCongestionEvent(uint16_t conn_id, bool congested) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id:" << loghex(conn_id) + << ", congested: " << congested; + dev->is_congested = congested; + GattOpsQueue::CongestionCallback(conn_id, congested); + } + + void OnReadAseState(uint16_t client_id, + uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, + void* data) { + + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << "unknown conn_id=" << loghex(conn_id); + return; + } + LOG(WARNING) << __func__; + + // check if the notification is for ASEs + ParseAseNotification(conn_id, handle, len, value); + } + + void OnReadOnlyPropertiesRead(uint16_t client_id, + uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t *value, void* data) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + uint8_t *p = value; + if (!dev) { + LOG(ERROR) << __func__ << "unknown conn_id=" << loghex(conn_id); + return; + } + + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_handle == handle) { + dev->num_ases_read++; + ParseAseParams(p, &it->ase_params, ASE_DIRECTION_SINK); + break; + } + } + + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_handle == handle) { + dev->num_ases_read++; + ParseAseParams(p, &it->ase_params, ASE_DIRECTION_SOURCE); + break; + } + } + + LOG(INFO) << __func__ << ": num_ases_read : " + << loghex(dev->num_ases_read); + + if(dev->num_ases_read == (dev->sink_ase_list.size() + + dev->src_ase_list.size())) { + sink_ase_value_list.clear(); + src_ase_value_list.clear(); + dev->discovery_completed = true; + // Now update using service discovery callback + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + for (auto it : dev->sink_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + sink_ase_value_list.push_back(ase); + } + for (auto it : dev->src_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + src_ase_value_list.push_back(ase); + } + AscsClientCallbacks *callback = iter->second; + // check if all ascs characteristics are read + // send out the callback as service discovery completed + callback->OnSearchComplete(0, dev->address, + sink_ase_value_list, + src_ase_value_list); + } + } + } + + static void OnReadOnlyPropertiesReadStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadOnlyPropertiesRead(client_id, conn_id, status, handle, + len, value, data); + } + + static void OnReadAseStateStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadAseState(client_id, conn_id, status, handle, + len, value, data); + } + + private: + uint8_t gatt_client_id = BTA_GATTS_INVALID_IF; + uint16_t ascs_client_id = 0; + AscsDevices ascsDevices; + // client id to callbacks mapping + std::map callbacks; + + void find_server_changed_ccc_handle(uint16_t conn_id, + const gatt::Service* service) { + AscsDevice* ascsDevice = ascsDevices.FindByConnId(conn_id); + if (!ascsDevice) { + LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD)) { + ascsDevice->srv_changed_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + if (!ascsDevice->srv_changed_ccc_handle) { + LOG(ERROR) << __func__ + << ": cannot find service changed CCC descriptor"; + continue; + } + LOG(INFO) << __func__ << " service_changed_ccc=" + << loghex(ascsDevice->srv_changed_ccc_handle); + break; + } + } + } + + // Find the handle for the client characteristics configuration of a given + // characteristics + uint16_t find_ccc_handle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + + if (!p_char) { + LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + + return 0; + } +}; + +const char* get_gatt_event_name(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BTA_GATTC_DEREG_EVT) + CASE_RETURN_STR(BTA_GATTC_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_CLOSE_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_CMPL_EVT) + CASE_RETURN_STR(BTA_GATTC_NOTIF_EVT) + CASE_RETURN_STR(BTA_GATTC_ENC_CMPL_CB_EVT) + CASE_RETURN_STR(BTA_GATTC_CONN_UPDATE_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_CHG_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_DISC_DONE_EVT) + CASE_RETURN_STR(BTA_GATTC_CONGEST_EVT) + default: + return "Unknown Event"; + } +} + +void ascs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + if (p_data == nullptr || !instance) return; + + LOG(INFO) << __func__ << ": Event : " << get_gatt_event_name(event); + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_OPEN_EVT: { + tBTA_GATTC_OPEN& o = p_data->open; + instance->OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + break; + } + + case BTA_GATTC_CLOSE_EVT: { + tBTA_GATTC_CLOSE& c = p_data->close; + instance->OnGattDisconnected(c.status, c.conn_id, c.client_if, + c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: + if (!p_data->notify.is_notify || + p_data->notify.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << p_data->notify.is_notify + << ", len=" << p_data->notify.len; + break; + } + instance->OnNotificationEvent(p_data->notify.conn_id, + p_data->notify.handle, p_data->notify.len, + p_data->notify.value); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_CONN_UPDATE_EVT: + instance->OnConnectionUpdateComplete(p_data->conn_update.conn_id, p_data); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + instance->OnServiceDiscDoneEvent(p_data->remote_bda); + break; + case BTA_GATTC_CONGEST_EVT: + instance->OnCongestionEvent(p_data->congest.conn_id, + p_data->congest.congested); + break; + default: + break; + } +} + +void encryption_callback(const RawAddress* address, tGATT_TRANSPORT, void*, + tBTM_STATUS status) { + if (instance) { + instance->OnEncryptionComplete(*address, + status == BTM_SUCCESS ? true : false); + } +} + +void AscsClient::Init(AscsClientCallbacks* callbacks) { + if (instance) { + instance->Register(callbacks); + } else { + instance = new AscsClientImpl(); + instance->Register(callbacks); + } +} + +void AscsClient::CleanUp(uint16_t client_id) { + if(instance->GetClientCount()) { + instance->Deregister(client_id); + if(!instance->GetClientCount()) { + delete instance; + instance = nullptr; + } + } +} + +AscsClient* AscsClient::Get() { + CHECK(instance); + return instance; +} + +} // namespace ascs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/connected_iso.cc b/le_audio/system/bt/bta/bap/connected_iso.cc new file mode 100644 index 0000000000000000000000000000000000000000..60dae7c366e5d132a69d40d9b99f97e87addac9c --- /dev/null +++ b/le_audio/system/bt/bta/bap/connected_iso.cc @@ -0,0 +1,1556 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "bta_bap_uclient_api.h" +#include "btm_int.h" +#include +#include "state_machine.h" +#include "stack/include/btm_ble_api_types.h" +#include "bt_trace.h" +#include "btif_util.h" +#include "osi/include/properties.h" + +namespace bluetooth { +namespace bap { +namespace cis { + +typedef struct { + uint8_t status; + uint16_t cis_handle; + uint8_t reason; +} tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM; + +typedef struct { + uint8_t status; + uint16_t conn_handle; +} tBTM_BLE_CIS_DATA_PATH_EVT_PARAM; + +typedef struct { +uint8_t status; +uint8_t cig_id; +} tBTM_BLE_SET_CIG_REMOVE_PARAM; + +struct CIS; +class CisInterfaceCallbacks; +using bluetooth::bap::cis::CisInterfaceCallbacks; + +struct tIsoSetUpDataPath { + uint16_t conn_handle; + uint8_t data_path_direction; + uint8_t data_path_id; +}; + +struct tIsoRemoveDataPath { + uint16_t conn_handle; + uint8_t data_path_direction; +}; + +enum IsoHciEvent { + CIG_CONFIGURE_REQ = 0, + CIG_CONFIGURED_EVT, + CIS_CREATE_REQ, + CIS_STATUS_EVT, + CIS_ESTABLISHED_EVT, + CIS_DISCONNECT_REQ, + CIS_DISCONNECTED_EVT, + CIG_REMOVE_REQ, + CIG_REMOVED_EVT, + SETUP_DATA_PATH_REQ, + SETUP_DATA_PATH_DONE_EVT, + REMOVE_DATA_PATH_REQ, + REMOVE_DATA_PATH_DONE_EVT, + CIS_CREATE_REQ_DUMMY +}; + +struct DataPathNode { + IsoHciEvent type; + union { + tIsoSetUpDataPath setup_datapath; + tIsoRemoveDataPath rmv_datapath; + }; +}; + +class CisStateMachine : public bluetooth::common::StateMachine { + public: + enum { + kStateIdle, + kStateSettingDataPath, + kStateReady, + kStateEstablishing, + kStateDestroying, + kStateEstablished, + }; + + class StateIdle : public State { + public: + StateIdle(CisStateMachine& sm) + : State(sm, kStateIdle), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Idle"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateSettingDataPath : public State { + public: + StateSettingDataPath(CisStateMachine& sm) + : State(sm, kStateSettingDataPath), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "SettingDataPath"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateReady : public State { + public: + StateReady(CisStateMachine& sm) + : State(sm, kStateReady), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Ready"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateDestroying : public State { + public: + StateDestroying(CisStateMachine& sm) + : State(sm, kStateDestroying), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Destroying"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateEstablishing : public State { + public: + StateEstablishing(CisStateMachine& sm) + : State(sm, kStateEstablishing), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Establishing"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateEstablished : public State { + public: + StateEstablished(CisStateMachine& sm) + : State(sm, kStateEstablished), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Established"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + CisStateMachine(CIS &cis) : + cis(cis) { + state_idle_ = new StateIdle(*this); + state_setting_data_path_ = new StateSettingDataPath(*this); + state_ready_ = new StateReady(*this); + state_destroying_ = new StateDestroying(*this); + state_establishing_ = new StateEstablishing(*this); + state_established_ = new StateEstablished(*this); + + AddState(state_idle_); + AddState(state_setting_data_path_); + AddState(state_ready_); + AddState(state_destroying_); + AddState(state_establishing_); + AddState(state_established_); + + SetInitialState(state_idle_); + } + + CIS &GetCis() { return cis; } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(CIG_CONFIGURE_REQ) + CASE_RETURN_STR(CIG_CONFIGURED_EVT) + CASE_RETURN_STR(CIS_CREATE_REQ) + CASE_RETURN_STR(CIS_STATUS_EVT) + CASE_RETURN_STR(CIS_ESTABLISHED_EVT) + CASE_RETURN_STR(CIS_DISCONNECT_REQ) + CASE_RETURN_STR(CIS_DISCONNECTED_EVT) + CASE_RETURN_STR(CIG_REMOVE_REQ) + CASE_RETURN_STR(CIG_REMOVED_EVT) + CASE_RETURN_STR(SETUP_DATA_PATH_REQ) + CASE_RETURN_STR(SETUP_DATA_PATH_DONE_EVT) + CASE_RETURN_STR(REMOVE_DATA_PATH_REQ) + CASE_RETURN_STR(REMOVE_DATA_PATH_DONE_EVT) + CASE_RETURN_STR(CIS_CREATE_REQ_DUMMY) + default: + return "Unknown Event"; + } + } + + private: + CIS &cis; + StateIdle *state_idle_; + StateSettingDataPath *state_setting_data_path_; + StateReady *state_ready_; + StateDestroying *state_destroying_; + StateEstablishing *state_establishing_; + StateEstablished *state_established_; +}; + +struct CIS { + uint8_t cig_id; + uint8_t cis_id; + uint16_t cis_handle; + bool to_air_setup_done; + bool from_air_setup_done; + uint8_t datapath_status; + uint8_t disc_direction; + uint8_t direction; // input or output or both + CisInterfaceCallbacks *cis_callback; + RawAddress peer_bda; + CISConfig cis_config; + CisStateMachine cis_sm; + CisState cis_state; + std::list datapath_queue; + + CIS(uint8_t cig_id, uint8_t cis_id, uint8_t direction, + CisInterfaceCallbacks* callback): + cig_id(cig_id), cis_id(cis_id), direction(direction), + cis_callback(callback), + cis_sm(*this) { + to_air_setup_done = false; + from_air_setup_done = false; + } +}; + +struct CreateCisNode { + uint8_t cig_id; + std::vector cis_ids; + std::vector cis_handles; + RawAddress peer_bda; +}; + +struct CIG { + CIGConfig cig_config; + CigState cig_state; + std::map clients_list; // address and count + std::map cis_list; // cis id to CIS +}; + +class CisInterfaceImpl; +CisInterfaceImpl *instance; + +static void hci_cig_param_callback(tBTM_BLE_SET_CIG_RET_PARAM *param); +static void hci_cig_param_test_callback(tBTM_BLE_SET_CIG_PARAM_TEST_RET *param); +static void hci_cig_remove_param_callback(uint8_t status, uint8_t cig_id); +static void hci_cis_create_status_callback( uint8_t status); +static void hci_cis_create_callback(tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param); +static void hci_cis_setup_datapath_callback( uint8_t status, + uint16_t conn_handle); +static void hci_cis_disconnect_callback(uint8_t status, uint16_t cis_handle, + uint8_t reason); + +void CisStateMachine::StateIdle::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); +} + +void CisStateMachine::StateIdle::OnExit() { + +} + +bool CisStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case SETUP_DATA_PATH_REQ: { + tIsoSetUpDataPath *data_path_info = (tIsoSetUpDataPath *) p_data; + tBTM_BLE_SET_ISO_DATA_PATH_PARAM p_params; + p_params.conn_handle = cis_.cis_handle; + p_params.data_path_dir = data_path_info->data_path_direction >> 1; + p_params.data_path_id = data_path_info->data_path_id; + p_params.codec_id[0] = 0x06; + memset(&p_params.codec_id[1], 0x00, sizeof(p_params.codec_id) - 1); + memset(&p_params.cont_delay, 0x00, sizeof(p_params.cont_delay)); + p_params.codec_config_length = 0x00; + p_params.codec_config = nullptr; + p_params.p_cb = &hci_cis_setup_datapath_callback; + if(BTM_BleSetIsoDataPath(&p_params) == HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateSettingDataPath); + DataPathNode node = { + .type = SETUP_DATA_PATH_REQ, + .setup_datapath = { + .conn_handle = cis_.cis_handle, + .data_path_direction = + data_path_info->data_path_direction, + .data_path_id = data_path_info->data_path_id + }, + }; + cis_.datapath_queue.push_back(node); + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + +void CisStateMachine::StateSettingDataPath::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); +} + +void CisStateMachine::StateSettingDataPath::OnExit() { + +} + +bool CisStateMachine::StateSettingDataPath::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case SETUP_DATA_PATH_REQ: { + // add them to the queue + tIsoSetUpDataPath *data_path_info = (tIsoSetUpDataPath *) p_data; + + DataPathNode node = { + .type = SETUP_DATA_PATH_REQ, + .setup_datapath = { + .conn_handle = cis_.cis_handle, + .data_path_direction = + data_path_info->data_path_direction, + .data_path_id = data_path_info->data_path_id + } + }; + + cis_.datapath_queue.push_back(node); + } break; + case SETUP_DATA_PATH_DONE_EVT: { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *param = + (tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *) p_data; + cis_.datapath_status = param->status; + + if(!cis_.datapath_queue.empty()) { + if(cis_.datapath_status == ISO_HCI_SUCCESS) { + DataPathNode node = cis_.datapath_queue.front(); + if(node.type == SETUP_DATA_PATH_REQ) { + uint8_t direction = node.setup_datapath.data_path_direction; + if(direction == DIR_TO_AIR) { + cis_.to_air_setup_done = true; + } else if( direction == DIR_FROM_AIR) { + cis_.from_air_setup_done = true; + } + } + } + // remove the entry as it is processed + cis_.datapath_queue.pop_front(); + } + + // check if there are any more entries in queue now + // expect the queue entry to be of setup datapath only + if(!cis_.datapath_queue.empty()) { + DataPathNode node = cis_.datapath_queue.front(); + if(node.type == SETUP_DATA_PATH_REQ) { + tBTM_BLE_SET_ISO_DATA_PATH_PARAM p_params; + p_params.conn_handle = node.setup_datapath.conn_handle; + p_params.data_path_dir = node.setup_datapath.data_path_direction >> 1; + p_params.data_path_id = node.setup_datapath.data_path_id; + p_params.codec_id[0] = 0x06; + memset(&p_params.codec_id[1], 0x00, sizeof(p_params.codec_id) - 1); + memset(&p_params.cont_delay, 0x00, sizeof(p_params.cont_delay)); + p_params.codec_config_length = 0x00; + p_params.codec_config = nullptr; + p_params.p_cb = &hci_cis_setup_datapath_callback; + if(BTM_BleSetIsoDataPath(&p_params) != HCI_SUCCESS) { + LOG(ERROR) << "Setup Datapath Failed"; + cis_.datapath_queue.pop_front(); + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } else { + LOG(ERROR) << "Unexpected entry"; + } + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + + +void CisStateMachine::StateReady::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + // update the ready state incase of transitioned from states except + // setting up datapath as CIG state event is sufficient for transition + // from setting up data path to ready. + if(cis_.cis_sm.PreviousStateId() != CisStateMachine::kStateSettingDataPath) { + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::READY); + } +} + +void CisStateMachine::StateReady::OnExit() { + +} + +bool CisStateMachine::StateReady::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case CIS_CREATE_REQ: { + tBTM_BLE_ISO_CREATE_CIS_CMD_PARAM cmd_data; + CreateCisNode *pNode = (CreateCisNode *) p_data; + cmd_data.cis_count = pNode->cis_ids.size(); + cmd_data.p_cb = &hci_cis_create_status_callback; + cmd_data.p_evt_cb = &hci_cis_create_callback; + tACL_CONN* acl = btm_bda_to_acl(pNode->peer_bda, BT_TRANSPORT_LE); + if(!acl) { + BTIF_TRACE_DEBUG("%s create_cis return ", __func__); + return false; + } + for (auto i: pNode->cis_handles) { + + tBTM_BLE_CHANNEL_MAP map = { .cis_conn_handle = i, + .acl_conn_handle = acl->hci_handle }; + cmd_data.link_conn_handles.push_back(map); + } + if(BTM_BleCreateCis(&cmd_data, &hci_cis_disconnect_callback) + == HCI_SUCCESS) + cis_.cis_sm.TransitionTo(CisStateMachine::kStateEstablishing); + } break; + case CIS_CREATE_REQ_DUMMY: { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateEstablishing); + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + + +void CisStateMachine::StateDestroying::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::DESTROYING); +} + +void CisStateMachine::StateDestroying::OnExit() { + +} + +bool CisStateMachine::StateDestroying::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case CIS_DISCONNECTED_EVT: { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *param = + (tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *) p_data; + if(param->status != ISO_HCI_SUCCESS) { + LOG(ERROR) <<__func__ << " cis disconnection failed"; + cis_.cis_sm.TransitionTo(cis_.cis_sm.PreviousStateId()); + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + + +void CisStateMachine::StateEstablishing::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::ESTABLISHING); +} + +void CisStateMachine::StateEstablishing::OnExit() { + +} + +bool CisStateMachine::StateEstablishing::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case CIS_STATUS_EVT: { + uint8_t status = *((uint8_t *)(p_data)); + if(status != ISO_HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + case CIS_ESTABLISHED_EVT: { + tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param = + (tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *) p_data; + if(param->status != ISO_HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateEstablished); + + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + +void CisStateMachine::StateEstablished::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + cis_.disc_direction = cis_.direction; + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::ESTABLISHED); +} + +void CisStateMachine::StateEstablished::OnExit() { + +} + +bool CisStateMachine::StateEstablished::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + switch (event) { + case CIS_DISCONNECT_REQ: + if(BTM_BleIsoCisDisconnect(cis_.cis_handle, 0x13 , + &hci_cis_disconnect_callback) == + HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateDestroying); + } + break; + case CIS_DISCONNECTED_EVT: { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *param = + (tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *) p_data; + if(param->status != ISO_HCI_SUCCESS) { + LOG(ERROR) <<__func__ << " cis disconnection failed"; + cis_.cis_sm.TransitionTo(cis_.cis_sm.PreviousStateId()); + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + default: + break; + } + return true; +} + +class CisInterfaceImpl : public CisInterface { + public: + CisInterfaceImpl(CisInterfaceCallbacks* callback): + callbacks(callback) { } + + ~CisInterfaceImpl() override = default; + + void CleanUp () { + + } + + CigState GetCigState(const uint8_t &cig_id) override { + CIG *cig = GetCig(cig_id); + if (cig != nullptr) { + return cig->cig_state; + } else { + return CigState::IDLE; + } + } + + CisState GetCisState(const uint8_t &cig_id, uint8_t cis_id) override { + return CisState::READY; + } + + uint8_t GetCisCount(const uint8_t &cig_id) override { + return 0; + } + + IsoHciStatus CreateCig(RawAddress client_peer_bda, bool reconfig, + CIGConfig &cig_config, + std::vector &cis_configs) override { + // check if CIG already exists + LOG(INFO) << __func__ << " : CIG Id = " << loghex(cig_config.cig_id); + CIG *cig = GetCig(cig_config.cig_id); + if (cig != nullptr) { + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + cig->clients_list.insert(std::make_pair(client_peer_bda, 0x01)); + } else { + if(!reconfig) { + // increment the count + it->second++; + } + } + // check if params are same for group requested + // and for the group alredy exists + if(cig->cig_state == CigState::CREATING) { + return ISO_HCI_IN_PROGRESS; + } else if(IsCigParamsSame(cig_config, cis_configs)) { + if(cig->cig_state == CigState::CREATED) { + return ISO_HCI_SUCCESS; + } + } + } + + // check if the CIS vector length is same as cis count passed + // in CIG confifuration + if(cig_config.cis_count != cis_configs.size()) { + return ISO_HCI_FAILED; + } + + char value[PROPERTY_VALUE_MAX] = {0}; + bool create_cig = false; + property_get("persist.vendor.btstack.get_cig_test_param", value, ""); + uint16_t ft_m_s, ft_s_m, iso_int, clk_accuracy, nse, pdu_m_s, pdu_s_m, bn_m_s, bn_s_m; + int res = sscanf(value, "%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu", &ft_m_s, &ft_s_m, &iso_int, + &clk_accuracy, &nse, &pdu_m_s, &pdu_s_m, &bn_m_s, &bn_s_m); + LOG(WARNING) << __func__<< ": FT_M_S: " << loghex(ft_m_s) << ", FT_S_M: " << loghex(ft_s_m) + << ", ISO_Interval: " << loghex(iso_int) << ", slave_clock: " << loghex(clk_accuracy) + << ", NSE: " << loghex(nse) << ", PDU_M_S:" << loghex(pdu_m_s) + << " PDU_S_M:" << loghex(pdu_s_m) << ", BN_M_S: " << loghex(bn_m_s) + << ", BN_S_M: " << loghex(bn_s_m); + if (res == 9) { + tBTM_BLE_SET_CIG_PARAM_TEST p_data_test; + p_data_test.cig_id = cig_config.cig_id; + memcpy(&p_data_test.sdu_int_s_to_m, &cig_config.sdu_interval_s_to_m, + sizeof(p_data_test.sdu_int_s_to_m)); + + memcpy(&p_data_test.sdu_int_m_to_s, &cig_config.sdu_interval_m_to_s, + sizeof(p_data_test.sdu_int_m_to_s)); + + p_data_test.ft_m_to_s = ft_m_s; + p_data_test.ft_s_to_m = ft_s_m; + p_data_test.iso_interval = iso_int; + p_data_test.slave_clock_accuracy = clk_accuracy; + p_data_test.packing = cig_config.packing; + p_data_test.framing = cig_config.framing; + p_data_test.cis_count = cig_config.cis_count; + + for (auto it = cis_configs.begin(); it != cis_configs.end();) { + tBTM_BLE_CIS_TEST_CONFIG cis_config; + cis_config.cis_id = it->cis_id; + cis_config.nse = nse; + cis_config.max_sdu_m_to_s = it->max_sdu_m_to_s; + cis_config.max_sdu_s_to_m = it->max_sdu_s_to_m; + cis_config.max_pdu_m_to_s = it->max_sdu_m_to_s; + cis_config.max_pdu_s_to_m = it->max_sdu_s_to_m; + cis_config.phy_m_to_s = it->phy_m_to_s; + cis_config.phy_s_to_m = it->phy_s_to_m; + cis_config.bn_m_to_s = bn_m_s; + cis_config.bn_s_to_m = 0; + if (cis_config.max_sdu_s_to_m > 0) { + cis_config.bn_s_to_m = bn_s_m; + if (cis_config.max_sdu_m_to_s > 0 && cis_config.nse > 13) { + cis_config.nse = 13; + } + } + p_data_test.cis_config.push_back(cis_config); + it++; + } + p_data_test.p_cb = &hci_cig_param_test_callback; + create_cig = (BTM_BleSetCigParametersTest(&p_data_test) == HCI_SUCCESS); + } else { + tBTM_BLE_ISO_SET_CIG_CMD_PARAM p_data; + p_data.cig_id = cig_config.cig_id; + memcpy(&p_data.sdu_int_s_to_m, &cig_config.sdu_interval_s_to_m, + sizeof(p_data.sdu_int_s_to_m)); + + memcpy(&p_data.sdu_int_m_to_s, &cig_config.sdu_interval_m_to_s, + sizeof(p_data.sdu_int_m_to_s)); + + p_data.slave_clock_accuracy = 0x00; + p_data.packing = cig_config.packing; + p_data.framing = cig_config.framing; + p_data.max_transport_latency_m_to_s = cig_config.max_tport_latency_m_to_s; + p_data.max_transport_latency_s_to_m = cig_config.max_tport_latency_s_to_m; + p_data.cis_count = cig_config.cis_count; + + for (auto it = cis_configs.begin(); it != cis_configs.end();) { + tBTM_BLE_CIS_CONFIG cis_config; + memcpy(&cis_config, &(*it), sizeof(tBTM_BLE_CIS_CONFIG)); + p_data.cis_config.push_back(cis_config); + it++; + } + p_data.p_cb = &hci_cig_param_callback; + create_cig = (BTM_BleSetCigParam(&p_data) == HCI_SUCCESS); + } + if(create_cig) { + // create new CIG and add it to the list + if(cig == nullptr) { + CIG *cig = new (CIG); + cig_list.insert(std::make_pair(cig_config.cig_id, cig)); + cig->cig_config = cig_config; + cig->cig_state = CigState::CREATING; + + for(uint8_t i = 0; i < cig_config.cis_count; i++) { + uint8_t direction = 0; + if(cis_configs[i].max_sdu_m_to_s) direction |= DIR_TO_AIR; + if(cis_configs[i].max_sdu_s_to_m) direction |= DIR_FROM_AIR; + + CIS *cis = new CIS(cig_config.cig_id, cis_configs[i].cis_id, + direction, callbacks); + cis->cis_config = cis_configs[i]; + cig->cis_list.insert(std::make_pair(cis_configs[i].cis_id, cis)); + } + + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + cig->clients_list.insert(std::make_pair(client_peer_bda, 0x01)); + } else { + // increment the count + it->second++; + LOG(WARNING) << __func__ << "count " << loghex(it->second); + } + } else { + cig->cig_config = cig_config; + cig->cig_state = CigState::CREATING; + + uint8_t i = 0; + for (auto it = cig->cis_list.begin(); it != cig->cis_list.end();) { + CIS *cis = it->second; + cis->cis_config = cis_configs[i]; + cis->cis_sm.TransitionTo(CisStateMachine::kStateIdle); + it++; i++; + } + + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + cig->clients_list.insert(std::make_pair(client_peer_bda, 0x01)); + } + } + return ISO_HCI_IN_PROGRESS; + } else { + return ISO_HCI_FAILED; + } + } + + IsoHciStatus RemoveCig(RawAddress client_peer_bda, uint8_t cig_id) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id); + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state == CigState::IDLE || + cig->cig_state == CigState::CREATING) { + return ISO_HCI_FAILED; + } else if(cig->cig_state == CigState::CREATED) { + + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + return ISO_HCI_FAILED; + } else { + // decrement the count + it->second--; + LOG(WARNING) << __func__ << ": Count : " << loghex(it->second); + } + + // check if all clients have voted off then go for CIG removal + uint8_t vote_on_count = 0; + for (auto it = cig->clients_list.begin(); + it != cig->clients_list.end();) { + vote_on_count += it->second; + it++; + } + + if(vote_on_count) { + LOG(WARNING) << __func__ << " : Vote On Count : " + << loghex(vote_on_count); + return ISO_HCI_SUCCESS; + } + + // check if any of the CIS are in established/streaming state + // if so return false as it is not allowed + if(IsCisActive(cig_id, 0xFF)) return ISO_HCI_FAILED; + + if(BTM_BleRemoveCig(cig_id, &hci_cig_remove_param_callback) + == HCI_SUCCESS) { + cig->cig_state = CigState::REMOVING; + return ISO_HCI_IN_PROGRESS; + } else return ISO_HCI_FAILED; + } + return ISO_HCI_FAILED; + } + + IsoHciStatus CreateCis(uint8_t cig_id, std::vector cis_ids, + RawAddress peer_bda) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id); + LOG(INFO) <<__func__ << ": No. of CISes = " << loghex(cis_ids.size()); + + IsoHciStatus ret; + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + bool cis_created = false; + CreateCisNode param; + param.cig_id = cig_id; + param.cis_ids = cis_ids; + param.peer_bda = peer_bda; + std::vector cis_handles; + + for (auto i: cis_ids) { + CIS *cis = GetCis(cig_id, i); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + cis_handles.push_back(cis->cis_handle); + } + param.cis_handles = cis_handles; + + for (auto i: cis_ids) { + LOG(INFO) <<__func__ << ": CIS Id = " << loghex(i); + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, i); + if (cis == nullptr) { + ret = ISO_HCI_FAILED; + break; + } + + cur_state = cis->cis_sm.StateId(); + + // check if CIS is already created or in progress + if(cur_state == CisStateMachine::kStateEstablishing) { + ret = ISO_HCI_IN_PROGRESS; + break; + } else if(cur_state == CisStateMachine::kStateEstablished) { + ret = ISO_HCI_SUCCESS; + break; + } else if(cur_state == CisStateMachine::kStateDestroying) { + ret = ISO_HCI_FAILED; + break; + } + if (cis_created == false) { + // queue it if there is pending create CIS + if (cis_queue.size()) { + // hand it over to the CIS module + // check if the new request is already exists + // as the head entry in the list + CreateCisNode& head = cis_queue.front(); + if(head.cig_id == cig_id && head.cis_ids == cis_ids && + head.peer_bda == peer_bda) { + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::CIS_CREATE_REQ, ¶m)) { + ret = ISO_HCI_IN_PROGRESS; + } else { + ret = ISO_HCI_FAILED; + break; + } + } else { + cis_queue.push_back(param); + } + } else { + cis_queue.push_back(param); + if(cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_CREATE_REQ, + ¶m)) { + ret = ISO_HCI_IN_PROGRESS; + } else { + ret = ISO_HCI_FAILED; + break; + } + } + cis_created = true; + } else { + if(cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_CREATE_REQ_DUMMY, + &peer_bda)) { + ret = ISO_HCI_IN_PROGRESS; + } else { + ret = ISO_HCI_FAILED; + break; + } + } + } + return ret; + } + + IsoHciStatus DisconnectCis(uint8_t cig_id, uint8_t cis_id, + uint8_t direction) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id) + << ": CIS Id = " << loghex(cis_id); + + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, cis_id); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + + if(cis->disc_direction & direction) { + // remove the direction bit form disc direciton + cis->disc_direction &= ~direction; + } + + if(cis->disc_direction) return ISO_HCI_SUCCESS; + + // if all directions are voted off go for CIS disconneciton + cur_state = cis->cis_sm.StateId(); + + // check if CIS is not created or in progress + if(cur_state == CisStateMachine::kStateReady) { + return ISO_HCI_SUCCESS; + } else if(cur_state == CisStateMachine::kStateEstablishing) { + return ISO_HCI_FAILED; + } else if(cur_state == CisStateMachine::kStateDestroying) { + return ISO_HCI_IN_PROGRESS; + } + + LOG(INFO) <<__func__ << " Request issued to CIS SM"; + // hand it over to the CIS module + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::CIS_DISCONNECT_REQ, nullptr)) { + return ISO_HCI_IN_PROGRESS; + } else return ISO_HCI_FAILED; + } + + IsoHciStatus SetupDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t data_path_direction, uint8_t data_path_id) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id) + << ": CIS Id = " << loghex(cis_id); + + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, cis_id); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + + cur_state = cis->cis_sm.StateId(); + + // check if CIS is not created or in progress + if(cur_state == CisStateMachine::kStateReady || + cur_state == CisStateMachine::kStateEstablishing || + cur_state == CisStateMachine::kStateDestroying) { + return ISO_HCI_FAILED; + } else if(cur_state == CisStateMachine::kStateEstablished) { + // return success as it is already created + return ISO_HCI_SUCCESS; + } + + // hand it over to the CIS module + tIsoSetUpDataPath data_path_info; + data_path_info.data_path_direction = data_path_direction; + data_path_info.data_path_id = data_path_id; + + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::SETUP_DATA_PATH_REQ, &data_path_info)) { + return ISO_HCI_IN_PROGRESS; + } else return ISO_HCI_FAILED; + } + + IsoHciStatus RemoveDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t data_path_direction) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id) + << ": CIS Id = " << loghex(cis_id); + + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, cis_id); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + + cur_state = cis->cis_sm.StateId(); + + // check if CIS is not created or in progress + if(cur_state == CisStateMachine::kStateReady || + cur_state == CisStateMachine::kStateEstablishing || + cur_state == CisStateMachine::kStateDestroying || + cur_state == CisStateMachine::kStateEstablished) { + return ISO_HCI_FAILED; + } + + // hand it over to the CIS module + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::REMOVE_DATA_PATH_REQ, &data_path_direction)) { + return ISO_HCI_SUCCESS; + } else return ISO_HCI_FAILED; + } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(CIG_CONFIGURED_EVT) + CASE_RETURN_STR(CIS_STATUS_EVT) + CASE_RETURN_STR(CIS_ESTABLISHED_EVT) + CASE_RETURN_STR(CIS_DISCONNECTED_EVT) + CASE_RETURN_STR(CIG_REMOVED_EVT) + CASE_RETURN_STR(SETUP_DATA_PATH_DONE_EVT) + CASE_RETURN_STR(REMOVE_DATA_PATH_DONE_EVT) + default: + return "Unknown Event"; + } + } + + IsoHciStatus ProcessEvent (uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": Event = " << GetEventName(event); + switch (event) { + case CIG_CONFIGURED_EVT: { + tBTM_BLE_SET_CIG_RET_PARAM *param = + (tBTM_BLE_SET_CIG_RET_PARAM *) p_data; + LOG(INFO) <<__func__ <<": CIG Id = " << loghex(param->cig_id) + << ": status = " << loghex(param->status); + + auto it = cig_list.find(param->cig_id); + if (it == cig_list.end()) { + return ISO_HCI_FAILED; + } + + if(!param->status) { + uint8_t i = 0; + CIG *cig = it->second; + tIsoSetUpDataPath data_path_info; + + for (auto it = cig->cis_list.begin(); + it != cig->cis_list.end(); it++) { + CIS *cis = it->second; + cis->cis_handle = *(param->conn_handle + i++); + cis->cis_sm.Start(); + if(cis->direction & DIR_TO_AIR) { + data_path_info.data_path_direction = DIR_TO_AIR; + data_path_info.data_path_id = 0x01; + cis->cis_sm.ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_REQ, + &data_path_info); + } + if(cis->direction & DIR_FROM_AIR) { + data_path_info.data_path_direction = DIR_FROM_AIR; + data_path_info.data_path_id = 0x01; + cis->cis_sm.ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_REQ, + &data_path_info); + } + } + } else { + // delete CIG and CIS + CIG *cig = it->second; + cig->cig_state = CigState::IDLE; + + while (!cig->cis_list.empty()) { + auto it = cig->cis_list.begin(); + CIS * cis = it->second; + cig->cis_list.erase(it); + delete cis; + } + callbacks->OnCigState(param->cig_id, CigState::IDLE); + cig_list.erase(it); + delete cig; + } + + } break; + case CIG_REMOVED_EVT: { + tBTM_BLE_SET_CIG_REMOVE_PARAM *param = + (tBTM_BLE_SET_CIG_REMOVE_PARAM *) p_data; + auto it = cig_list.find(param->cig_id); + if (it == cig_list.end()) { + return ISO_HCI_FAILED; + } else { + // delete CIG and CIS + CIG *cig = it->second; + while (!cig->cis_list.empty()) { + auto it = cig->cis_list.begin(); + CIS * cis = it->second; + cig->cis_list.erase(it); + delete cis; + } + cig->cig_state = CigState::IDLE; + cig_list.erase(it); + callbacks->OnCigState(param->cig_id, CigState::IDLE); + delete cig; + } + } break; + case CIS_STATUS_EVT: { + // clear the first entry from cis queue and send the next + // CIS creation request queue it if there is pending create CIS + CreateCisNode &head = cis_queue.front(); + for (auto i: head.cis_ids) { + CIS *cis = GetCis(head.cig_id, i); + if(cis) { + cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_STATUS_EVT, p_data); + } + } + } break; + case CIS_ESTABLISHED_EVT: { + tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param = + (tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *) p_data; + LOG(INFO) << __func__ << ": CIS handle = " + << loghex(param->connection_handle) + << ": Status = " << loghex(param->status); + CIS *cis = GetCis(param->connection_handle); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_ESTABLISHED_EVT, p_data); + } + bool cis_status = false; + if (cis_queue.size()) { + cis_queue.pop_front(); + } + while(cis_queue.size() && !cis_status) { + CreateCisNode &head = cis_queue.front(); + CIS *cis = GetCis(head.cig_id, head.cis_ids[0]); + if(cis == nullptr || + cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + // remove the entry + cis_queue.pop_front(); + } else if(cis) { + IsoHciStatus hci_status = CreateCis(head.cig_id, head.cis_ids, + head.peer_bda); + if(hci_status == ISO_HCI_SUCCESS || + hci_status == ISO_HCI_IN_PROGRESS) { + cis_status = true; + } else { + // remove the entry + cis_queue.pop_front(); + } + } + } + } break; + case CIS_DISCONNECTED_EVT: { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *param = + (tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *) p_data; + CIS *cis = GetCis(param->cis_handle); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_DISCONNECTED_EVT, p_data); + } + } break; + case SETUP_DATA_PATH_DONE_EVT: { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *param = + (tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *) p_data; + + CIS *cis = GetCis(param->conn_handle); + CIG *cig = nullptr; + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_DONE_EVT, + p_data); + } + uint8_t cig_id = cis->cig_id; + + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + break; + } else { + // delete CIG and CIS + cig = it->second; + } + + uint8_t num_cis_is_ready = 0; + for(auto it = cig->cis_list.begin(); it != cig->cis_list.end(); it++) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateReady) { + num_cis_is_ready++; + } + } + + // check if all setup data paths are completed + if(num_cis_is_ready == cig->cis_list.size()) { + cig->cig_state = CigState::CREATED; + callbacks->OnCigState(cig_id, CigState::CREATED); + } + } break; + case REMOVE_DATA_PATH_DONE_EVT: { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *param = + (tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *) p_data; + CIS *cis = GetCis(param->conn_handle); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::REMOVE_DATA_PATH_DONE_EVT, + p_data); + } + } break; + default: + break; + } + return ISO_HCI_SUCCESS; + } + + private: + std::map cig_list; // cig id to CIG structure + std::list cis_queue; + CisInterfaceCallbacks *callbacks; + // 0xFF will be passed for cis id in case search is for any of the + // CIS part of that group + bool IsCisActive(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_active = false; + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + return is_cis_active; + } else { + CIG *cig = it->second; + if(cis_id != 0XFF) { + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_active = true; + } + } + } else { + for (auto it : cig->cis_list) { + CIS *cis = it.second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_active = true; + break; + } + } + } + } + return is_cis_active; + } + + bool IsCigParamsSame(CIGConfig &cig_config, + std::vector &cis_configs) { + CIG *cig = GetCig(cig_config.cig_id); + bool is_params_same = true; + uint8_t i = 0; + + if(cig == nullptr || (cis_configs.size() != cig->cig_config.cis_count)) { + LOG(WARNING) << __func__ << ": Count is different "; + return false; + } + + if(cig->cig_config.cig_id != cig_config.cig_id || + cig->cig_config.cis_count != cig_config.cis_count || + cig->cig_config.packing != cig_config.packing || + cig->cig_config.framing != cig_config.framing || + cig->cig_config.max_tport_latency_m_to_s != + cig_config.max_tport_latency_m_to_s || + cig->cig_config.max_tport_latency_s_to_m != + cig_config.max_tport_latency_s_to_m || + cig->cig_config.sdu_interval_m_to_s[0] != + cig_config.sdu_interval_m_to_s[0] || + cig->cig_config.sdu_interval_m_to_s[1] != + cig_config.sdu_interval_m_to_s[1] || + cig->cig_config.sdu_interval_m_to_s[2] != + cig_config.sdu_interval_m_to_s[2] || + cig->cig_config.sdu_interval_s_to_m[0] != + cig_config.sdu_interval_s_to_m[0] || + cig->cig_config.sdu_interval_s_to_m[1] != + cig_config.sdu_interval_s_to_m[1] || + cig->cig_config.sdu_interval_s_to_m[2] != + cig_config.sdu_interval_s_to_m[2]) { + LOG(WARNING) << __func__ << " cig params are different "; + return false; + } + + for (auto it = cig->cis_list.begin(); it != cig->cis_list.end();) { + CIS *cis = it->second; + if(cis->cis_config.cis_id == cis_configs[i].cis_id && + cis->cis_config.max_sdu_m_to_s == cis_configs[i].max_sdu_m_to_s && + cis->cis_config.max_sdu_s_to_m == cis_configs[i].max_sdu_s_to_m && + cis->cis_config.phy_m_to_s == cis_configs[i].phy_m_to_s && + cis->cis_config.phy_s_to_m == cis_configs[i].phy_s_to_m && + cis->cis_config.rtn_m_to_s == cis_configs[i].rtn_m_to_s && + cis->cis_config.rtn_s_to_m == cis_configs[i].rtn_s_to_m) { + it++; i++; + } else { + is_params_same = false; + break; + } + } + LOG(WARNING) << __func__ << ": is_params_same : " + << loghex(is_params_same); + return is_params_same; + } + + bool IsCisExists(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_exists = false; + auto it = cig_list.find(cig_id); + if (it != cig_list.end()) { + CIG *cig = it->second; + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + is_cis_exists = true; + } + } + return is_cis_exists; + } + + CIS *GetCis(uint8_t cig_id, uint8_t cis_id) { + auto it = cig_list.find(cig_id); + if (it != cig_list.end()) { + CIG *cig = it->second; + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + return it->second; + } + } + return nullptr; + } + + CIG *GetCig(uint8_t cig_id) { + auto it = cig_list.find(cig_id); + if (it != cig_list.end()) { + return it->second; + } + return nullptr; + } + + CIS *GetCis(uint16_t cis_handle) { + bool cis_found = false; + CIS *cis = nullptr; + for (auto it : cig_list) { + CIG *cig = it.second; + if(cig->cig_state == CigState::CREATED || + cig->cig_state == CigState::CREATING) { + for (auto it : cig->cis_list) { + cis = it.second; + if(cis->cis_handle == cis_handle) { + cis_found = true; + break; + } + } + } + if(cis_found) return cis; + } + return nullptr; + } + + // TODO to remove if there is no need + bool IsCisEstablished(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_established = false; + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + return false; + } else { + CIG *cig = it->second; + if(cis_id != 0XFF) { + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_established = true; + } + } + } else { + for (auto it : cig->cis_list) { + CIS *cis = it.second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_established = true; + break; + } + } + } + } + return is_cis_established; + } + + // TODO to remove if there is no need + bool IsCisStreaming(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_streaming = false; + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + return false; + } else { + CIG *cig = it->second; + if(cis_id != 0XFF) { + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_streaming = true; + } + } + } else { + for (auto it : cig->cis_list) { + CIS *cis = it.second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_streaming = true; + break; + } + } + } + } + return is_cis_streaming; + } +}; + +void CisInterface::Initialize( + CisInterfaceCallbacks* callbacks) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + } else { + instance = new CisInterfaceImpl(callbacks); + } +} + +void CisInterface::CleanUp() { + + CisInterfaceImpl* ptr = instance; + instance = nullptr; + ptr->CleanUp(); + delete ptr; +} + +CisInterface* CisInterface::Get() { + CHECK(instance); + return instance; +} + +static void hci_cig_param_callback(tBTM_BLE_SET_CIG_RET_PARAM *param) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIG_CONFIGURED_EVT, param); + } +} + +static void hci_cig_param_test_callback(tBTM_BLE_SET_CIG_PARAM_TEST_RET *param) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIG_CONFIGURED_EVT, param); + } +} + +static void hci_cig_remove_param_callback(uint8_t status, uint8_t cig_id) { + tBTM_BLE_SET_CIG_REMOVE_PARAM param = { .status = status, + .cig_id = cig_id }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIG_REMOVED_EVT, ¶m); + } +} + +static void hci_cis_create_status_callback ( uint8_t status) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIS_STATUS_EVT, &status); + } +} + +static void hci_cis_create_callback ( + tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIS_ESTABLISHED_EVT, param); + } +} + +static void hci_cis_setup_datapath_callback ( uint8_t status, + uint16_t conn_handle) { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM param = { .status = status, + .conn_handle = conn_handle }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_DONE_EVT, ¶m); + } +} + +static void hci_cis_disconnect_callback ( uint8_t status, uint16_t cis_handle, + uint8_t reason) { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM param = { .status = status, + .cis_handle = cis_handle, + .reason = reason + }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIS_DISCONNECTED_EVT, ¶m); + } +} + +#if 0 +static void hci_cis_remove_datapath_callback ( uint8_t status, + uint16_t conn_handle) { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM param = { .status = status, + .conn_handle = conn_handle }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::REMOVE_DATA_PATH_DONE_EVT, ¶m); + } +} +#endif + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/gattc_ops_queue.cc b/le_audio/system/bt/bta/bap/gattc_ops_queue.cc new file mode 100644 index 0000000000000000000000000000000000000000..7c7b187d0f8501a48985b451f7f34cebe2f981e5 --- /dev/null +++ b/le_audio/system/bt/bta/bap/gattc_ops_queue.cc @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gattc_ops_queue.h" + +#include +#include +#include + +namespace bluetooth { +namespace bap { + +using gatt_operation = GattOpsQueue::gatt_operation; +using bluetooth::Uuid; + +constexpr uint8_t GATT_READ_CHAR = 1; +constexpr uint8_t GATT_READ_DESC = 2; +constexpr uint8_t GATT_WRITE_CHAR = 3; +constexpr uint8_t GATT_WRITE_DESC = 4; +constexpr uint8_t GATT_SERV_SEARCH = 5; + +struct gatt_read_op_data { + BAP_GATT_READ_OP_CB cb; + void* cb_data; +}; + +std::unordered_map> + GattOpsQueue::gatt_op_queue; +std::unordered_set GattOpsQueue::gatt_op_queue_executing; + +std::unordered_map GattOpsQueue::congestion_queue; + +void GattOpsQueue::mark_as_not_executing(uint16_t conn_id) { + gatt_op_queue_executing.erase(conn_id); +} + +void GattOpsQueue::gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + gatt_read_op_data* tmp = (gatt_read_op_data*)data; + BAP_GATT_READ_OP_CB tmp_cb = tmp->cb; + void* tmp_cb_data = tmp->cb_data; + + APPL_TRACE_DEBUG("%s: conn_id=0x%x handle=%d status=%d len=%d", __func__, + conn_id, handle, status, len); + + osi_free(data); + + auto map_ptr = gatt_op_queue.find(conn_id); + if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, + conn_id); + return; + } + + std::list& gatt_ops = map_ptr->second; + gatt_operation op = gatt_ops.front(); + gatt_ops.pop_front(); + + mark_as_not_executing(conn_id); + gatt_execute_next_op(conn_id); + + if (tmp_cb) { + tmp_cb(op.client_id, conn_id, status, handle, len, value, tmp_cb_data); + return; + } +} + +struct gatt_write_op_data { + BAP_GATT_WRITE_OP_CB cb; + void* cb_data; +}; + +void GattOpsQueue::gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + gatt_write_op_data* tmp = (gatt_write_op_data*)data; + BAP_GATT_WRITE_OP_CB tmp_cb = tmp->cb; + void* tmp_cb_data = tmp->cb_data; + + APPL_TRACE_DEBUG("%s: conn_id=0x%x handle=%d status=%d", __func__, conn_id, + handle, status); + + osi_free(data); + + auto map_ptr = gatt_op_queue.find(conn_id); + if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, + conn_id); + return; + } + + std::list& gatt_ops = map_ptr->second; + gatt_operation op = gatt_ops.front(); + gatt_ops.pop_front(); + + mark_as_not_executing(conn_id); + gatt_execute_next_op(conn_id); + + if (tmp_cb) { + tmp_cb(op.client_id, conn_id, status, handle, tmp_cb_data); + return; + } +} + +void GattOpsQueue::gatt_execute_next_op(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + if (gatt_op_queue.empty()) { + APPL_TRACE_DEBUG("%s: op queue is empty", __func__); + return; + } + + auto ptr = congestion_queue.find(conn_id); + + if (ptr != congestion_queue.end()) { + bool is_congested = ptr->second; + APPL_TRACE_DEBUG("%s: congestion queue exist, conn_id: %d, is_congested: %d", + __func__, conn_id, is_congested); + if(is_congested) { + APPL_TRACE_DEBUG("%s: lower layer is congested", __func__); + return; + } + } + + auto map_ptr = gatt_op_queue.find(conn_id); + + if (map_ptr == gatt_op_queue.end()) { + APPL_TRACE_DEBUG("%s: Queue is null", __func__); + return; + } + + if (map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: queue is empty for conn_id: %d", __func__, + conn_id); + return; + } + + if (gatt_op_queue_executing.count(conn_id)) { + APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__); + return; + } + + gatt_op_queue_executing.insert(conn_id); + + std::list& gatt_ops = map_ptr->second; + + gatt_operation& op = gatt_ops.front(); + + APPL_TRACE_DEBUG("%s: op.type=%d, handle=%d", __func__, op.type, + op.handle); + if (op.type == GATT_READ_CHAR) { + gatt_read_op_data* data = + (gatt_read_op_data*)osi_malloc(sizeof(gatt_read_op_data)); + data->cb = op.read_cb; + data->cb_data = op.read_cb_data; + BTA_GATTC_ReadCharacteristic(conn_id, op.handle, GATT_AUTH_REQ_NONE, + gatt_read_op_finished, data); + + } else if (op.type == GATT_READ_DESC) { + gatt_read_op_data* data = + (gatt_read_op_data*)osi_malloc(sizeof(gatt_read_op_data)); + data->cb = op.read_cb; + data->cb_data = op.read_cb_data; + BTA_GATTC_ReadCharDescr(conn_id, op.handle, GATT_AUTH_REQ_NONE, + gatt_read_op_finished, data); + + } else if (op.type == GATT_WRITE_CHAR) { + gatt_write_op_data* data = + (gatt_write_op_data*)osi_malloc(sizeof(gatt_write_op_data)); + data->cb = op.write_cb; + data->cb_data = op.write_cb_data; + BTA_GATTC_WriteCharValue(conn_id, op.handle, op.write_type, + std::move(op.value), GATT_AUTH_REQ_NONE, + gatt_write_op_finished, data); + + } else if (op.type == GATT_WRITE_DESC) { + gatt_write_op_data* data = + (gatt_write_op_data*)osi_malloc(sizeof(gatt_write_op_data)); + data->cb = op.write_cb; + data->cb_data = op.write_cb_data; + BTA_GATTC_WriteCharDescr(conn_id, op.handle, std::move(op.value), + GATT_AUTH_REQ_NONE, gatt_write_op_finished, data); + } else if (op.type == GATT_SERV_SEARCH) { + BTA_GATTC_ServiceSearchRequest(conn_id, op.p_srvc_uuid); + } +} + +void GattOpsQueue::Clean(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + + gatt_op_queue.erase(conn_id); + gatt_op_queue_executing.erase(conn_id); +} + +void GattOpsQueue::ReadCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_READ_CHAR, + .client_id = client_id, + .handle = handle, + .read_cb = cb, + .read_cb_data = cb_data}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::ReadDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_READ_DESC, + .client_id = client_id, + .handle = handle, + .read_cb = cb, + .read_cb_data = cb_data}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::WriteCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, + BAP_GATT_WRITE_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_WRITE_CHAR, + .client_id = client_id, + .handle = handle, + .write_type = write_type, + .write_cb = cb, + .write_cb_data = cb_data, + .value = std::move(value)}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::WriteDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, + BAP_GATT_WRITE_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_WRITE_DESC, + .client_id = client_id, + .handle = handle, + .write_type = write_type, + .write_cb = cb, + .write_cb_data = cb_data, + .value = std::move(value)}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::ServiceSearch(uint16_t client_id, + uint16_t conn_id, Uuid* srvc_uuid) { + gatt_op_queue[conn_id].push_back({.type = GATT_SERV_SEARCH, + .client_id = client_id, + .p_srvc_uuid = srvc_uuid}); + gatt_execute_next_op(conn_id); +} + +uint16_t GattOpsQueue::ServiceSearchComplete(uint16_t conn_id, + tGATT_STATUS status) { + auto map_ptr = gatt_op_queue.find(conn_id); + if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, + conn_id); + return 0; + } + + std::list& gatt_ops = map_ptr->second; + + gatt_operation gatt_op = gatt_ops.front(); + gatt_ops.pop_front(); + mark_as_not_executing(conn_id); + gatt_execute_next_op(conn_id); + return gatt_op.client_id; +} + +void GattOpsQueue::CongestionCallback(uint16_t conn_id, bool congested) { + congestion_queue[conn_id] = congested; + if(!congested) { + gatt_execute_next_op(conn_id); + } +} + +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/gattc_ops_queue.h b/le_audio/system/bt/bta/bap/gattc_ops_queue.h new file mode 100644 index 0000000000000000000000000000000000000000..4dd952536fd2783824413012cdfefeb365956a9f --- /dev/null +++ b/le_audio/system/bt/bta/bap/gattc_ops_queue.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include "bta_gatt_api.h" + +typedef void (*BAP_GATT_READ_OP_CB)(uint16_t client_id,uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t* value, + void* data); + +typedef void (*BAP_GATT_WRITE_OP_CB)(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, void* data); + +/* BTA GATTC implementation does not allow for multiple commands queuing. So one + * client making calls to BTA_GATTC_ReadCharacteristic, BTA_GATTC_ReadCharDescr, + * BTA_GATTC_WriteCharValue, BTA_GATTC_WriteCharDescr must wait for the callacks + * before scheduling next operation. + * + * Methods below can be used as replacement to BTA_GATTC_* in BTA app. They do + * queue the commands if another command is currently being executed. + * + * If you decide to use those methods in your app, make sure to not mix it with + * existing BTA_GATTC_* API. + */ + +namespace bluetooth { +namespace bap { + +class GattOpsQueue { + public: + static void Clean(uint16_t conn_id); + static void ReadCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data); + static void ReadDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data); + static void WriteCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, + BAP_GATT_WRITE_OP_CB cb, void* cb_data); + static void WriteDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, BAP_GATT_WRITE_OP_CB cb, + void* cb_data); + static void ServiceSearch(uint16_t client_id, + uint16_t conn_id, Uuid* p_srvc_uuid); + + static uint16_t ServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status); + + static void CongestionCallback(uint16_t conn_id, bool congested); + + + /* Holds pending GATT operations */ + struct gatt_operation { + uint8_t type; + uint16_t client_id; + uint16_t handle; + BAP_GATT_READ_OP_CB read_cb; + void* read_cb_data; + BAP_GATT_WRITE_OP_CB write_cb; + void* write_cb_data; + + /* write-specific fields */ + tGATT_WRITE_TYPE write_type; + std::vector value; + + /* discovery specific */ + Uuid* p_srvc_uuid; + }; + + private: + static bool is_congested; + + static void mark_as_not_executing(uint16_t conn_id); + static void gatt_execute_next_op(uint16_t conn_id); + static void gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data); + static void gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data); + + // maps connection id to operations waiting for execution + static std::unordered_map> gatt_op_queue; + + // maps connection id to congestion status of each device + static std::unordered_map congestion_queue; + + // contain connection ids that currently execute operations + static std::unordered_set gatt_op_queue_executing; +}; + +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/gatts_ops_queue.cc b/le_audio/system/bt/bta/bap/gatts_ops_queue.cc new file mode 100644 index 0000000000000000000000000000000000000000..b84fe6593fa5c4d4c38dc0b281eb34813e0d6c84 --- /dev/null +++ b/le_audio/system/bt/bta/bap/gatts_ops_queue.cc @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gatts_ops_queue.h" + +#include +#include +#include + +namespace bluetooth { +namespace bap { + +using gatts_operation = GattsOpsQueue::gatts_operation; +using bluetooth::Uuid; + +constexpr uint8_t GATT_NOTIFY = 1; + +std::unordered_map> + GattsOpsQueue::gatts_op_queue; +std::unordered_set GattsOpsQueue::gatts_op_queue_executing; +std::unordered_map GattsOpsQueue::congestion_queue; + +void GattsOpsQueue::mark_as_not_executing(uint16_t conn_id) { + gatts_op_queue_executing.erase(conn_id); +} + +void GattsOpsQueue::gatts_execute_next_op(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + + if (gatts_op_queue.empty()) { + APPL_TRACE_DEBUG("%s: op queue is empty", __func__); + return; + } + + auto ptr = congestion_queue.find(conn_id); + + if (ptr != congestion_queue.end()) { + bool is_congested = ptr->second; + APPL_TRACE_DEBUG("%s: congestion queue exist, conn_id: %d, is_congested: %d", + __func__, conn_id, is_congested); + if(is_congested) { + APPL_TRACE_DEBUG("%s: lower layer is congested", __func__); + return; + } + } + + auto map_ptr = gatts_op_queue.find(conn_id); + + if (map_ptr == gatts_op_queue.end()) { + APPL_TRACE_DEBUG("%s: Queue is null", __func__); + return; + } + + if (map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: queue is empty for conn_id: %d", __func__, + conn_id); + return; + } + + if (gatts_op_queue_executing.count(conn_id)) { + APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__); + return; + } + + std::list& gatts_ops = map_ptr->second; + gatts_operation& op = gatts_ops.front(); + APPL_TRACE_DEBUG("%s: op.type=%d, attr_id=%d", + __func__, op.type, op.attr_id); + + if(op.type == GATT_NOTIFY) { + if(GATTS_CheckStatusForApp(conn_id,op.need_confirm) == GATT_SUCCESS) { + BTA_GATTS_HandleValueIndication(conn_id, op.attr_id, op.value, op.need_confirm); + gatts_op_queue_executing.insert(conn_id); + } + } +} + +void GattsOpsQueue::Clean(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + + gatts_op_queue.erase(conn_id); + gatts_op_queue_executing.erase(conn_id); +} + +void GattsOpsQueue::SendNotification(uint16_t conn_id, + uint16_t handle, + std::vector value, + bool need_confirm) { + gatts_op_queue[conn_id].push_back({.type = GATT_NOTIFY, + .attr_id = handle, + .value = value, + .need_confirm = need_confirm}); + gatts_execute_next_op(conn_id); +} + +void GattsOpsQueue::NotificationCallback(uint16_t conn_id){ + auto map_ptr = gatts_op_queue.find(conn_id); + if (map_ptr == gatts_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", + __func__, conn_id); + return; + } + + std::list& gatts_ops = map_ptr->second; + gatts_operation op = gatts_ops.front(); + gatts_ops.pop_front(); + mark_as_not_executing(conn_id); + gatts_execute_next_op(conn_id); +} + +void GattsOpsQueue::CongestionCallback(uint16_t conn_id, bool congested) { + APPL_TRACE_DEBUG("%s: conn_id: %d, congested: %d", + __func__, conn_id,congested); + + congestion_queue[conn_id] = congested; + if(!congested) { + gatts_execute_next_op(conn_id); + } +} + +} +} // namespace ends diff --git a/le_audio/system/bt/bta/bap/gatts_ops_queue.h b/le_audio/system/bt/bta/bap/gatts_ops_queue.h new file mode 100644 index 0000000000000000000000000000000000000000..6c0d5a855c949b07de99845a70a8f85baae0386e --- /dev/null +++ b/le_audio/system/bt/bta/bap/gatts_ops_queue.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include "bta_gatt_api.h" +namespace bluetooth { +namespace bap { + +class GattsOpsQueue { + public: + static void Clean(uint16_t conn_id); + static void SendNotification(uint16_t conn_id, uint16_t handle, std::vector value, bool need_confirm); + static void NotificationCallback(uint16_t conn_id); + static void CongestionCallback(uint16_t conn_id, bool congested); + + /* Holds pending GATT operations */ + struct gatts_operation { + uint8_t type; + uint16_t attr_id; + std::vector value; + bool need_confirm; + }; + + private: + static bool is_congested; + static void mark_as_not_executing(uint16_t conn_id); + static void gatts_execute_next_op(uint16_t conn_id); + + // maps connection id to operations waiting for execution + static std::unordered_map> gatts_op_queue; + + // maps connection id to congestion status of each device + static std::unordered_map congestion_queue; + + // contain connection ids that currently execute operations + static std::unordered_set gatts_op_queue_executing; + +}; // Class GattsOpsQueue ends + +} +} // namespace ends diff --git a/le_audio/system/bt/bta/bap/pacs_client.cc b/le_audio/system/bt/bta/bap/pacs_client.cc new file mode 100644 index 0000000000000000000000000000000000000000..2123dcf0e6bfbaed64cec830497e19f05b16617b --- /dev/null +++ b/le_audio/system/bt/bta/bap/pacs_client.cc @@ -0,0 +1,1862 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bta_gatt_api.h" +#include "bta_pacs_client_api.h" +#include "gattc_ops_queue.h" +#include +#include +#include +#include +#include "stack/btm/btm_int.h" +#include "device/include/controller.h" +#include "osi/include/properties.h" + +#include +#include "btif/include/btif_bap_config.h" +#include "osi/include/log.h" +#include "btif_util.h" +#include +#include "btif_bap_codec_utils.h" + +namespace bluetooth { +namespace bap { +namespace pacs { + +//using bluetooth::bap::pacs::PacsClientCallbacks; +using base::Closure; +using bluetooth::bap::GattOpsQueue; + +Uuid PACS_UUID = Uuid::FromString("1850"); +Uuid PACS_SINK_PAC_UUID = Uuid::FromString("2BC9"); +Uuid PACS_SINK_LOC_UUID = Uuid::FromString("2BCA"); +Uuid PACS_SRC_PAC_UUID = Uuid::FromString("2BCB"); +Uuid PACS_SRC_LOC_UUID = Uuid::FromString("2BCC"); +Uuid PACS_AVA_AUDIO_UUID = Uuid::FromString("2BCD"); +Uuid PACS_SUP_AUDIO_UUID = Uuid::FromString("2BCE"); + +class PacsClientImpl; +PacsClientImpl* instance; + +typedef uint8_t codec_type_t[5]; + +constexpr uint8_t SINK_PAC = 0x01; +constexpr uint8_t SRC_PAC = 0x02; +constexpr uint8_t SINK_LOC = 0x04; +constexpr uint8_t SRC_LOC = 0x08; +constexpr uint8_t AVAIL_CONTEXTS = 0x10; +constexpr uint8_t SUPP_CONTEXTS = 0x20; + +constexpr uint8_t LTV_TYPE_SUP_FREQS = 0x01; +constexpr uint8_t LTV_TYPE_SUP_FRAME_DUR = 0x02; +constexpr uint8_t LTV_TYPE_CHNL_COUNTS = 0x03; +constexpr uint8_t LTV_TYPE_OCTS_PER_FRAME = 0x04; +constexpr uint8_t LTV_TYPE_MAX_SUP_FRAMES_PER_SDU = 0x05; + +constexpr uint8_t LTV_TYPE_PREF_AUD_CONTEXT = 0x01; +constexpr uint8_t LTV_TYPE_VS_META_DATA = 0xFF;//TODO +constexpr uint16_t QTI_ID = 0x000A; + +constexpr uint8_t LTV_TYPE_VS_META_DATA_LC3Q = 0x10; + +//constexpr uint16_t SAMPLE_RATE_NONE = 0x0; +constexpr uint16_t SAMPLE_RATE_8K = 0x1 << 0; +//constexpr uint16_t SAMPLE_RATE_11K = 0x1 << 1; +constexpr uint16_t SAMPLE_RATE_16K = 0x1 << 2; +//constexpr uint16_t SAMPLE_RATE_22K = 0x1 << 3; +constexpr uint16_t SAMPLE_RATE_24K = 0x1 << 4; +constexpr uint16_t SAMPLE_RATE_32K = 0x1 << 5; +constexpr uint16_t SAMPLE_RATE_441K = 0x1 << 6; +constexpr uint16_t SAMPLE_RATE_48K = 0x1 << 7; +constexpr uint16_t SAMPLE_RATE_882K = 0x1 << 8; +constexpr uint16_t SAMPLE_RATE_96K = 0x1 << 9; +constexpr uint16_t SAMPLE_RATE_176K = 0x1 << 10; +constexpr uint16_t SAMPLE_RATE_192K = 0x1 << 11; +//constexpr uint16_t SAMPLE_RATE_384K = 0x1 << 12; + +constexpr uint8_t CODEC_ID_LC3 = 0x06; +constexpr uint8_t DISCOVER_SUCCESS = 0x00; +constexpr uint8_t DISCOVER_FAIL = 0xFF; + +std::map freq_map = { + {SAMPLE_RATE_8K, CodecSampleRate::CODEC_SAMPLE_RATE_8000 }, + {SAMPLE_RATE_16K, CodecSampleRate::CODEC_SAMPLE_RATE_16000 }, + {SAMPLE_RATE_24K, CodecSampleRate::CODEC_SAMPLE_RATE_24000 }, + {SAMPLE_RATE_32K, CodecSampleRate::CODEC_SAMPLE_RATE_32000 }, + {SAMPLE_RATE_441K, CodecSampleRate::CODEC_SAMPLE_RATE_44100 }, + {SAMPLE_RATE_48K, CodecSampleRate::CODEC_SAMPLE_RATE_48000 }, + {SAMPLE_RATE_882K, CodecSampleRate::CODEC_SAMPLE_RATE_88200 }, + {SAMPLE_RATE_96K, CodecSampleRate::CODEC_SAMPLE_RATE_96000 }, + {SAMPLE_RATE_176K, CodecSampleRate::CODEC_SAMPLE_RATE_176400}, + {SAMPLE_RATE_192K, CodecSampleRate::CODEC_SAMPLE_RATE_192000} +}; + +// ltv type to length +std::map ltv_info = { + {LTV_TYPE_SUP_FREQS, 0x03}, + {LTV_TYPE_SUP_FRAME_DUR, 0x02}, + {LTV_TYPE_CHNL_COUNTS, 0x02}, + {LTV_TYPE_OCTS_PER_FRAME, 0x05}, + {LTV_TYPE_MAX_SUP_FRAMES_PER_SDU, 0x02}, + {LTV_TYPE_PREF_AUD_CONTEXT, 0x03} +}; + +enum class ProfleOP { + CONNECT, + DISCONNECT +}; + +struct ProfileOperation { + uint16_t client_id; + ProfleOP type; +}; + +enum class DevState { + IDLE = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +struct SinkPacsData { + uint16_t sink_pac_handle; + uint16_t sink_pac_ccc_handle; + std::vector sink_pac_records; + bool read_sink_pac_record; +}; + +struct SrcPacsData { + uint16_t src_pac_handle; + uint16_t src_pac_ccc_handle; + std::vector src_pac_records; + bool read_src_pac_record; +}; + +void pacs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +void encryption_callback(const RawAddress*, tGATT_TRANSPORT, void*, + tBTM_STATUS); + +struct PacsDevice { + RawAddress address; + /* This is true only during first connection to profile, until we store the + * device */ + bool first_connection; + bool service_changed_rcvd; + + /* we are making active attempt to connect to this device, 'direct connect'. + * This is true only during initial phase of first connection. */ + bool connecting_actively; + + uint16_t conn_id; + std::vectorsink_info; + std::vectorsrc_info; + uint16_t sink_loc_handle; + uint16_t sink_loc_ccc_handle; + uint16_t src_loc_handle; + uint16_t src_loc_ccc_handle; + uint16_t avail_contexts_handle; + uint16_t avail_contexts_ccc_handle; + uint16_t supp_contexts_handle; + uint16_t supp_contexts_ccc_handle; + uint16_t srv_changed_ccc_handle; + uint8_t chars_read; + uint8_t chars_to_be_read; + std::vector consolidated_sink_pac_records; + std::vector consolidated_src_pac_records; + uint32_t sink_locations; + uint32_t src_locations; + uint32_t available_contexts; + uint32_t supported_contexts; + bool discovery_completed; + bool notifications_enabled; + DevState state; + bool is_congested; + std::vector profile_queue; + std::vector connected_client_list; //list client requested for connection + PacsDevice(const RawAddress& address) : address(address) {} + PacsDevice() { + first_connection = false; + service_changed_rcvd = false; + conn_id = 0; + sink_loc_handle = 0; + sink_loc_ccc_handle = 0; + src_loc_handle = 0; + src_loc_ccc_handle = 0; + avail_contexts_handle = 0; + avail_contexts_ccc_handle = 0; + supp_contexts_handle = 0; + supp_contexts_ccc_handle = 0; + srv_changed_ccc_handle = 0; + chars_read = 0; + sink_locations = 0; + src_locations = 0; + available_contexts = 0; + supported_contexts = 0; + discovery_completed = false; + notifications_enabled = false; + state = static_cast(0); + is_congested = false; + } +}; + +class PacsDevices { + public: + void Add(PacsDevice device) { + if (FindByAddress(device.address) != nullptr) return; + + devices.push_back(device); + } + + void Remove(const RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->address != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + PacsDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const PacsDevice& device) { + return device.address == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + PacsDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const PacsDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector devices; +}; + +class PacsClientImpl : public PacsClient { + public: + ~PacsClientImpl() override = default; + + PacsClientImpl() : gatt_client_id(BTA_GATTS_INVALID_IF) {}; + + bool Register(PacsClientCallbacks *callback) { + // looks for client is already registered + bool is_client_registered = false; + for (auto it : callbacks) { + PacsClientCallbacks *pac_callback = it.second; + if (callback == pac_callback) { + is_client_registered = true; + break; + } + } + + LOG(WARNING) << __func__ << " is_client_registered: " + << is_client_registered + << ", gatt_client_id: " << gatt_client_id; + if (is_client_registered) return false; + + if (gatt_client_id == BTA_GATTS_INVALID_IF) { + BTA_GATTC_AppRegister( + pacs_gattc_callback, + base::Bind( + [](PacsClientCallbacks *callback, uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start PACS profile - no gatt " + "clients left!"; + return; + } + + if (instance) { + LOG(WARNING) << " PACS gatt_client_id: " + << instance->gatt_client_id; + instance->gatt_client_id = client_id; + instance->callbacks.insert(std::make_pair( + ++instance->pacs_client_id, callback)); + callback->OnInitialized(0, instance->pacs_client_id); + } + }, + callback), true); + } else { + instance->callbacks.insert(std::make_pair( + ++instance->pacs_client_id, callback)); + callback->OnInitialized(0, instance->pacs_client_id); + } + return true; + } + + bool Deregister (uint16_t client_id) { + bool status = false; + auto it = callbacks.find(client_id); + if (it != callbacks.end()) { + callbacks.erase(it); + if(callbacks.empty()) { + // deregister with GATT + LOG(WARNING) << __func__ << " Gatt de-register from pacs"; + BTA_GATTC_AppDeregister(gatt_client_id); + gatt_client_id = BTA_GATTS_INVALID_IF; + } + status = true; + } + return status; + } + + uint8_t GetClientCount () { + return callbacks.size(); + } + + void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) override { + LOG(WARNING) << __func__ << " address: " << address; + PacsDevice *dev = pacsDevices.FindByAddress(address); + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::CONNECT; + + if (dev == nullptr) { + PacsDevice pac_dev(address); + pacsDevices.Add(pac_dev); + dev = pacsDevices.FindByAddress(address); + } + if (dev == nullptr) { + LOG(ERROR) << __func__ << "dev is null"; + return; + } + + LOG(WARNING) << __func__ << ": state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::IDLE: { + BTA_GATTC_Open(gatt_client_id, address, is_direct, + GATT_TRANSPORT_LE, false); + dev->state = DevState::CONNECTING; + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTING: { + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTED: { + // add it to the client id list if not already done + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id](uint16_t id) { + return id == client_id; + }); + + if (iter == dev->connected_client_list.end()) + dev->connected_client_list.push_back(client_id); + + // respond immediately as connected + + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + } + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << __func__ <<": Device not connected to profile: " << address; + return; + } + + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::DISCONNECT; + + LOG(WARNING) << __func__ << ": address: " << address + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTING: { + auto iter = std::find_if(dev->profile_queue.begin(), + dev->profile_queue.end(), + [&client_id]( ProfileOperation entry) { + return ((entry.type == ProfleOP::CONNECT) && + (entry.client_id == client_id)); + }); + // If it is the last client requested for disconnect + if (iter != dev->profile_queue.end() && + dev->profile_queue.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + } else { + // clear the connection queue and + // move the state to DISCONNECTING to better track + dev->profile_queue.clear(); + } + dev->state = DevState::DISCONNECTING; + dev->profile_queue.push_back(op); + } else { + // remove the connection entry from the list + // as the same client has requested for disconnection + dev->profile_queue.erase(iter); + } + } break; + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // if it is the last client requested for disconnection + if (iter != dev->connected_client_list.end() && + dev->connected_client_list.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->profile_queue.push_back(op); + dev->state = DevState::DISCONNECTING; + } + } else { + // remove the client from connected_client_list + dev->connected_client_list.erase(iter); + // remove the pending gatt ops( not the ongoing one ) + // initiated from client which requested disconnect + // TODO and send callback as disconnected + } + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + default: + break; + } + } + + void StartDiscovery(uint16_t client_id, const RawAddress& address) override { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << __func__ << ": Device not connected to profile: " << address; + return; + } + LOG(WARNING) << __func__ << " address: " << address + << ", state: " << static_cast(dev->state); + switch(dev->state) { + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + LOG(WARNING) << __func__ + << ": client_id: " << client_id + << ", stored_client_id:" << stored_client_id; + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if (iter == dev->connected_client_list.end()) { + break; + } + + LOG(WARNING) << __func__ + << ", discovery_completed: " << dev->discovery_completed + << ", notifications_enabled: " << dev->notifications_enabled; + + // check if the discovery is already finished + // send back the same results to the other client + if (dev->discovery_completed && dev->notifications_enabled) { + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + LOG(WARNING) << __func__ << ": OnSearchComplete"; + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_SUCCESS, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + break; + } + + // reset it + dev->chars_read = 0x00; + dev->chars_to_be_read = 0x00; + dev->sink_info.clear(); + dev->src_info.clear(); + dev->consolidated_sink_pac_records.clear(); + dev->consolidated_src_pac_records.clear(); + //TODO + //btif_bap_remove_record() + + // queue the request to GATT queue module + GattOpsQueue::ServiceSearch(client_id, dev->conn_id, &PACS_UUID); + } break; + + default: + break; + } + } + + void GetAudioAvailability(uint16_t client_id, const RawAddress& address) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << __func__ << ": Device not connected to profile: " << address; + return; + } + LOG(WARNING) << __func__ << ": address: " << address + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if (iter == dev->connected_client_list.end()) { + break; + } + + // check if the discovery is already finished + // send back the same results to the other client + if (dev->discovery_completed && dev->notifications_enabled) { + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnAudioContextAvailable(dev->address, + dev->available_contexts); + } + break; + } + + // queue the request to GATT queue module + GattOpsQueue::ReadCharacteristic( + client_id, dev->conn_id, dev->avail_contexts_handle, + PacsClientImpl::OnReadAvailableAudioStatic, nullptr); + } break; + default: + LOG(WARNING) << __func__ << " default"; + break; + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBTA_TRANSPORT transport, uint16_t mtu) { + + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + /* When device is quickly disabled and enabled in settings, this case + * might happen */ + LOG(WARNING) << __func__ + <<"Closing connection to non pacs device, address: " + << address; + BTA_GATTC_Close(conn_id); + return; + } + + LOG(WARNING) << __func__ << " address: " << address + << ", state: " << static_cast(dev->state) + << ", status: " << loghex(status); + + if (dev->state == DevState::CONNECTING) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << __func__ << ": Failed to connect to PACS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, + ConnectionState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + pacsDevices.Remove(address); + return; + } + } else if (dev->state == DevState::DISCONNECTING) { + // TODO will this happens ? + // it could have called the cancel open to expect the + // open cancelled event + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Failed to connect to PACS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, + ConnectionState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + pacsDevices.Remove(address); + return; + } else { + // gatt connected successfully + // if the disconnect entry is found we need to initiate the + // gatt disconnect. may be a race condition just after sending + // cancel open gatt connected event received + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + break; + } + } else { + it++; + } + } + return; + } + } else { + // return unconditinally + return; + } + + // success scenario code + dev->conn_id = conn_id; + + tACL_CONN* p_acl = btm_bda_to_acl(address, BT_TRANSPORT_LE); + if (p_acl != nullptr && + controller_get_interface()->supports_ble_2m_phy() && + HCI_LE_2M_PHY_SUPPORTED(p_acl->peer_le_features)) { + LOG(INFO) << address << " set preferred PHY to 2M"; + BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0); + } + + /* verify bond */ + uint8_t sec_flag = 0; + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + /* if link has been encrypted */ + OnEncryptionComplete(address, true); + return; + } + + if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + /* if bonded and link not encrypted */ + sec_flag = BTM_BLE_SEC_ENCRYPT; + LOG(WARNING) << "trying to encrypt now"; + BTM_SetEncryption(address, BTA_TRANSPORT_LE, encryption_callback, + nullptr, sec_flag); + return; + } + + /* otherwise let it go through */ + OnEncryptionComplete(address, true); + } + + void OnGattDisconnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress remote_bda, + tBTA_GATT_REASON reason) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ + << ": Skipping unknown device disconnect, conn_id=" + << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << " remote_bda: " << remote_bda + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTING: { + // sudden disconnection + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, + ConnectionState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + } break; + case DevState::CONNECTED: { + // sudden disconnection + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + auto iter = callbacks.find(*it); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, + ConnectionState::DISCONNECTED); + } + it = dev->connected_client_list.erase(it); + } + } break; + case DevState::DISCONNECTING: { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, + ConnectionState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + it = dev->connected_client_list.erase(it); + } + // check if the connection queue is not empty + // if not initiate the Gatt connection + } break; + default: + break; + } + + if (dev->conn_id) { + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->conn_id = 0; + } + + dev->state = DevState::IDLE; + pacsDevices.Remove(remote_bda); + } + + void OnConnectionUpdateComplete(uint16_t conn_id, tBTA_GATTC* p_data) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ + << ": Skipping unknown device, conn_id=" + << loghex(conn_id); + return; + } + } + + void OnEncryptionComplete(const RawAddress& address, bool success) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device" << address; + return; + } + + if(dev->state != DevState::CONNECTING) { + LOG(ERROR) << __func__ << ": received in wrong state" << address; + return; + } + + LOG(WARNING) << __func__ << ": address=" << address << loghex(success); + // encryption failed + if (!success) { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, + ConnectionState::DISCONNECTED); + } + // change the type to disconnect + it->type = ProfleOP::DISCONNECT; + } else { + it++; + } + } + dev->state = DevState::DISCONNECTING; + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + BTA_GATTC_Close(dev->conn_id); + } else { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + dev->connected_client_list.push_back(it->client_id); + PacsClientCallbacks *callback = iter->second; + LOG(WARNING) << __func__ << " calling OnConnectionState"; + callback->OnConnectionState(address, ConnectionState::CONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::CONNECTED; + } + } + + void OnServiceChangeEvent(const RawAddress& address) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device: " << address; + return; + } + LOG(INFO) << __func__ << ": address: " << address; + dev->first_connection = true; + dev->service_changed_rcvd = true; + GattOpsQueue::Clean(dev->conn_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device: " << address; + return; + } + + LOG(WARNING) << __func__ << " service_changed_rcvd: " + << dev->service_changed_rcvd; + if (dev->service_changed_rcvd) { + // queue the request to GATT queue module with dummu client id + GattOpsQueue::ServiceSearch(0XFF, dev->conn_id, &PACS_UUID); + } + } + + void RegisterForNotification(uint16_t client_id, uint16_t conn_id, + PacsDevice* dev, uint16_t ccc_handle, + uint16_t handle) { + if(handle && ccc_handle) { + /* Register and enable Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + conn_id, dev->address, handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + } + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor( + client_id, conn_id, ccc_handle, + std::move(value), GATT_WRITE, nullptr, nullptr); + } + } + + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device, conn_id = " + << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id = " << loghex(conn_id); + + uint16_t client_id = GattOpsQueue::ServiceSearchComplete(conn_id, + status); + LOG(WARNING) << __func__ << ": client_id = " << loghex(client_id); + auto iter = callbacks.find(client_id); + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << __func__ << ": Service discovery failed"; + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_FAIL, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + return; + } + + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* service = nullptr; + if (services) { + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) { + LOG(INFO) << __func__ << ": Found UUID_CLASS_GATT_SERVER, handle=" + << loghex(tmp.handle); + const gatt::Service* service_changed_service = &tmp; + find_server_changed_ccc_handle(conn_id, service_changed_service); + } else if (tmp.uuid == PACS_UUID) { + LOG(INFO) << __func__ << ": Found PACS service, handle=" + << loghex(tmp.handle); + service = &tmp; + } + } + } else { + LOG(ERROR) << __func__ + << ": no services found for conn_id: " << loghex(conn_id); + return; + } + + if (!service) { + LOG(ERROR) << __func__ << ": No PACS service found"; + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_FAIL, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + return; + } + + for (const gatt::Characteristic& charac : service->characteristics) { + LOG(INFO) << __func__ << ": uuid: " << charac.uuid; + if (charac.uuid == PACS_SINK_PAC_UUID) { + LOG(INFO) << __func__ << ": sink pac uuid found. "; + + SinkPacsData info; + memset(&info, 0, sizeof(info)); + info.sink_pac_handle = charac.value_handle; + info.sink_pac_ccc_handle = find_ccc_handle(conn_id, charac.value_handle); + dev->sink_info.push_back(info); + dev->chars_to_be_read |= SINK_PAC; + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + if (info.sink_pac_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + info.sink_pac_ccc_handle, + info.sink_pac_handle); + } + + } else if (charac.uuid == PACS_SINK_LOC_UUID) { + LOG(INFO) << __func__ << ": sink loc uuid found. "; + dev->sink_loc_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id,conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->sink_loc_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + + dev->chars_to_be_read |= SINK_LOC; + if (dev->sink_loc_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->sink_loc_ccc_handle, + dev->sink_loc_handle); + } + + } else if (charac.uuid == PACS_SRC_PAC_UUID) { + LOG(INFO) << __func__ << ": src pac uuid found. "; + + SrcPacsData info; + memset(&info, 0, sizeof(info)); + info.src_pac_handle = charac.value_handle; + info.src_pac_ccc_handle = find_ccc_handle(conn_id, charac.value_handle); + dev->src_info.push_back(info); + dev->chars_to_be_read |= SRC_PAC; + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + if (info.src_pac_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + info.src_pac_ccc_handle, + info.src_pac_handle); + } + + } else if (charac.uuid == PACS_SRC_LOC_UUID) { + LOG(INFO) << __func__ << ": src loc uuid found. "; + dev->src_loc_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->src_loc_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + dev->chars_to_be_read |= SRC_LOC; + if (dev->src_loc_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->src_loc_ccc_handle, + dev->src_loc_handle); + } + + } else if (charac.uuid == PACS_AVA_AUDIO_UUID) { + LOG(INFO) << __func__ << ": avaliable audio uuid found. "; + dev->avail_contexts_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->avail_contexts_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + dev->chars_to_be_read |= AVAIL_CONTEXTS; + if (dev->avail_contexts_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->avail_contexts_ccc_handle, + dev->avail_contexts_handle); + } + + } else if (charac.uuid == PACS_SUP_AUDIO_UUID) { + LOG(INFO) << __func__ << ": supported audio uuid found. "; + dev->supp_contexts_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->supp_contexts_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + dev->chars_to_be_read |= SUPP_CONTEXTS; + if (dev->supp_contexts_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->supp_contexts_ccc_handle, + dev->supp_contexts_handle); + } + } else { + LOG(WARNING) << "Unknown characteristic found:" << charac.uuid; + } + } + + dev->notifications_enabled = true; + + LOG(INFO) << __func__ + << ": service_changed_rcvd: " << dev->service_changed_rcvd; + if (dev->service_changed_rcvd) { + dev->service_changed_rcvd = false; + } + } + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id: " << loghex(conn_id); + + if(dev->avail_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->available_contexts, p); + } + } + + void OnCongestionEvent(uint16_t conn_id, bool congested) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id=" << loghex(conn_id) + << ", congested: " << congested; + dev->is_congested = congested; + GattOpsQueue::CongestionCallback(conn_id, congested); + } + + void OnReadAvailableAudio(uint16_t client_id, + uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, + void* data) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << ": unknown conn_id: " << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id: " << loghex(conn_id); + + if(dev->avail_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->available_contexts, p); + // check if all pacs characteristics are read + // send out the callback as service discovery completed + // get the callback and update the upper layers + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnAudioContextAvailable(dev->address, + dev->available_contexts); + } + } + } + + bool IsRecordReadable(uint16_t total_len, uint16_t processed_len, + uint16_t req_len) { + LOG(WARNING) << __func__ << ": processed_len: " << loghex(processed_len) + << ", req_len: " << loghex(req_len); + if((total_len > processed_len) && + ((total_len - processed_len) >= req_len)) { + return true; + } else { + return false; + } + } + + bool IsLtvValid(uint8_t ltv_type, uint16_t ltv_len) { + bool valid = true; + for (auto it : ltv_info) { + if(ltv_type == it.first && + ltv_len != it.second) { + valid = false; + break; + } + } + return valid; + } + + void ParsePacRecord (PacsDevice *dev, uint16_t handle, uint16_t total_len, + uint8_t *value, void* data) { + std::vector pac_records; + CodecIndex codec_type; + uint8_t *p = value; + codec_type_t codec_id; + bool stop_reading = false; + uint8_t codec_cap_len; + std::vector codec_caps; + uint8_t meta_data_len; + std::vector meta_data; + uint16_t processed_len = 0; + uint8_t num_pac_recs; + uint16_t context_type; + + SinkPacsData* sinkinfo = FindSinkByHandle(dev, handle); + SrcPacsData* srcinfo = FindSrcByHandle(dev, handle); + + // Number_of_PAC_records is 1 byte + if (!total_len) { + LOG(ERROR) << __func__ + << ": zero len record, total_len: "; + return; + } + + STREAM_TO_UINT8(num_pac_recs, p); + processed_len ++; + + LOG(WARNING) << __func__ << ": num_pac_recs: " << loghex(num_pac_recs) + << ", total_len: " << loghex(total_len); + while (!stop_reading && num_pac_recs) { + // reset context type for before reading record + context_type = ucast::CONTENT_TYPE_UNSPECIFIED; + // read the complete record + // codec_id is of 5 bytes. + if (!IsRecordReadable(total_len, processed_len, sizeof(codec_id))) { + LOG(ERROR) << __func__ << ": Not valid codec id, Bad pacs record."; + break; + } + + STREAM_TO_ARRAY(&codec_id, p, static_cast (sizeof(codec_id))); + + processed_len += static_cast (sizeof(codec_id)); + + if (codec_id[0] == CODEC_ID_LC3) { + LOG(INFO) << __func__ << ": LC3 codec "; + codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + } else { + // TODO to check for vendor codecs + break; + } + + // codec_cap_len is of 1 byte + if (!IsRecordReadable(total_len, processed_len, 1)) { + LOG(ERROR) << __func__ << ": Not valid codec id, Bad pacs record."; + break; + } + + STREAM_TO_UINT8(codec_cap_len, p); + processed_len ++; + + LOG(WARNING) << __func__ + << ": codec_cap_len: " << loghex(codec_cap_len) + << ": processed_len: " << loghex(processed_len); + + if (!codec_cap_len) { + LOG(ERROR) << __func__ + << ": Invalid codec cap len"; + break; + } + + if (!IsRecordReadable(total_len, processed_len, codec_cap_len)) { + LOG(ERROR) << __func__ << ": not enough data, Bad pacs record."; + break; + } + + codec_caps.resize(codec_cap_len); + STREAM_TO_ARRAY(codec_caps.data(), p, codec_cap_len); + uint8_t len = codec_cap_len; + uint8_t *pp = codec_caps.data(); + + // Now look for supported freq LTV entry + while (len) { + LOG(WARNING) << __func__ << ": len: " << loghex(len); + + if (!IsRecordReadable(total_len, processed_len, 1)) { + LOG(ERROR) << __func__ << ": not enough data, Bad pacs record."; + break; + } + uint8_t ltv_len = *pp++; + len--; + processed_len++; + + LOG(WARNING) << __func__ << ": ltv_len: " << loghex(ltv_len); + if (!ltv_len || + !IsRecordReadable(total_len, processed_len, ltv_len)) { + LOG(ERROR) << __func__ << ": Not valid ltv length"; + stop_reading = true; + break; + } + + processed_len += ltv_len; + + // get type and value + uint8_t ltv_type = *pp++; + LOG(WARNING) << __func__ << ": ltv_type: " << loghex(ltv_type); + if(!IsLtvValid(ltv_type, ltv_len)) { + LOG(ERROR) << __func__ << ": No ltv type to length match"; + stop_reading = true; + break; + } + if(ltv_type == LTV_TYPE_SUP_FREQS) { + uint16_t supp_freqs; + STREAM_TO_UINT16(supp_freqs, pp); + LOG(WARNING) << __func__ << ": supp_freqs: " << supp_freqs; + + for (auto it : freq_map) { + if(supp_freqs & it.first) { + CodecConfig codec_config; + codec_config.codec_type = codec_type; + codec_config.sample_rate = it.second; + pac_records.push_back(codec_config); + } + } + } else { + uint8_t rem_len = ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++; }; + } + + if (len >= ltv_len) { + len -= ltv_len; + } else { + LOG(ERROR) << __func__ << "wrong len"; + len = 0; + } + } + + LOG(WARNING) << __func__ << ": stop_reading: " << stop_reading; + if (stop_reading) break; + + // set the default chnl count to mono as it is optional + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + it->channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + } + + // now check for other LTV values + len = codec_cap_len; + pp = codec_caps.data(); + while (len) { + LOG(WARNING) << __func__ + << ": checking other LTV values,len: " << loghex(len); + uint8_t ltv_len = *pp++; + len--; + LOG(WARNING) << __func__ << ": ltv_len: " << loghex(ltv_len); + + //get type and value + uint8_t ltv_type = *pp++; + LOG(WARNING) << __func__ << ": ltv_type: " << loghex(ltv_type); + if(ltv_type == LTV_TYPE_SUP_FRAME_DUR) { + uint8_t supp_frames; + STREAM_TO_UINT8(supp_frames, pp); + LOG(WARNING) << __func__ + << ": pac rec len: " << loghex(pac_records.size()); + for (auto it = pac_records.begin(); it != pac_records.end(); + ++it) { + UpdateCapaSupFrameDurations(&(*it), supp_frames); + } + } else if (ltv_type == LTV_TYPE_CHNL_COUNTS) { + uint8_t chnl_allocation; + STREAM_TO_UINT8(chnl_allocation, pp); + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + it->channel_mode = + static_cast (chnl_allocation); + } + } else if (ltv_type == LTV_TYPE_OCTS_PER_FRAME) { + uint32_t octs_per_frame; + STREAM_TO_UINT32(octs_per_frame, pp); + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaSupOctsPerFrame(&(*it), octs_per_frame); + } + } else if (ltv_type == LTV_TYPE_MAX_SUP_FRAMES_PER_SDU) { + uint32_t max_sup_frames_per_sdu; + STREAM_TO_UINT32(max_sup_frames_per_sdu, pp); + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaMaxSupLc3Frames(&(*it), max_sup_frames_per_sdu); + } + } else { + uint8_t rem_len = ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + + if(len >= ltv_len) { + len -= ltv_len; + } else { + LOG(ERROR) << __func__ << ": wrong len"; + len = 0; + } + } + + //Meta data length 1 byte + if (!IsRecordReadable(total_len, processed_len, 1)) { + LOG(ERROR) << __func__ << ": Not valid meta data len, Bad pacs record."; + break; + } + + STREAM_TO_UINT8(meta_data_len, p); + processed_len ++; + LOG(WARNING) << __func__ << ": meta_data_len: " << loghex(meta_data_len) + << ": processed_len: " << loghex(processed_len); + + if (meta_data_len) { + if (!IsRecordReadable(total_len, processed_len, meta_data_len)) { + LOG(ERROR) << __func__ << ": not enough data, Bad pacs record."; + break; + } + + meta_data.resize(meta_data_len); + STREAM_TO_ARRAY(meta_data.data(), p, meta_data_len); + uint8_t len = meta_data_len; + uint8_t *pp = meta_data.data(); + + while (len) { + LOG(WARNING) << __func__ << ": len: " << loghex(len); + uint8_t ltv_len = *pp++; + len--; + processed_len++; + + LOG(WARNING) << __func__ << ": ltv_len: " << loghex(ltv_len); + if (!ltv_len || + !IsRecordReadable(total_len, processed_len, ltv_len)) { + LOG(ERROR) << __func__ << ": Not valid ltv length"; + stop_reading = true; + break; + } + + processed_len += ltv_len; + + // get type and value + uint8_t ltv_type = *pp++; + + LOG(WARNING) << __func__ << ": ltv_type: " << loghex(ltv_type); + + if (!IsLtvValid(ltv_type, ltv_len)) { + LOG(ERROR) << __func__ << ": No ltv type to length match"; + stop_reading = true; + break; + } + + if (ltv_type == LTV_TYPE_PREF_AUD_CONTEXT) { + STREAM_TO_UINT16(context_type, pp); + LOG(WARNING) << __func__ + << ": ltv_context_type: " << loghex(context_type); + + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaPreferredContexts(&(*it), context_type); + } + } else if (ltv_type == LTV_TYPE_VS_META_DATA) { + uint16_t company_id; + STREAM_TO_UINT16(company_id, pp); + + //total vs meta data length(meta length -4) in bytes. + uint8_t total_vendor_ltv_len = meta_data_len - 4; + LOG(WARNING) << __func__ + << ": total_vendor_ltv_len: " << loghex(total_vendor_ltv_len); + + if (company_id == QTI_ID) { + while (total_vendor_ltv_len) { + uint8_t vs_meta_data_len = *pp++; + LOG(WARNING) << __func__ + << ": vs_meta_data_len: " << loghex(vs_meta_data_len); + + // get type and value + uint8_t vs_meta_data_type = *pp++; + LOG(WARNING) << __func__ + << ": vs_meta_data_type: " << loghex(vs_meta_data_type); + + if (vs_meta_data_type == LTV_TYPE_VS_META_DATA_LC3Q) { + uint8_t vs_meta_data_value[vs_meta_data_len - 1]; + STREAM_TO_ARRAY(&vs_meta_data_value, pp, + static_cast (sizeof(vs_meta_data_value))); + + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaVendorMetaDataLc3QPref(&(*it), true); + UpdateCapaVendorMetaDataLc3QVer(&(*it), vs_meta_data_value[0]); + } + } else { + //TODO check for other ltvs + uint8_t rem_len = vs_meta_data_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + + /* 5bytes (VS length bypte + Meta datatype + + company ID(2 bytes) + Lc3q length) */ + if(total_vendor_ltv_len >= (vs_meta_data_len + 5)) { + total_vendor_ltv_len -= (vs_meta_data_len + 5); + len = total_vendor_ltv_len; + } else { + LOG(ERROR) << __func__ << ": wrong len."; + total_vendor_ltv_len = 0; + } + } + } else { + //TODO check for other comany IDs + uint8_t rem_len = total_vendor_ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + } else { + uint8_t rem_len = ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + + if (len >= ltv_len) { + len -= ltv_len; + } else { + LOG(ERROR) << __func__ << ": wrong len"; + len = 0; + } + } + } + + if (sinkinfo != nullptr) { + // Now update all records to conf file + while (!pac_records.empty()) { + CodecConfig record = pac_records.back(); + sinkinfo->sink_pac_records.push_back(record); + pac_records.pop_back(); + btif_bap_add_record(dev->address, REC_TYPE_CAPABILITY, + context_type, CodecDirection::CODEC_DIR_SINK, + &record); + } + } else if (srcinfo != nullptr) { + // Now update all records to conf file + while (!pac_records.empty()) { + CodecConfig record = pac_records.back(); + srcinfo->src_pac_records.push_back(record); + pac_records.pop_back(); + btif_bap_add_record(dev->address, REC_TYPE_CAPABILITY, + context_type, CodecDirection::CODEC_DIR_SRC, + &record); + } + } + num_pac_recs--; + } + + if (sinkinfo != nullptr) { + sinkinfo->read_sink_pac_record = true; + bool all_sink_pacs_read = false; + for (auto it = dev->sink_info.begin(); + it != dev->sink_info.end(); it ++) { + if (it->read_sink_pac_record == true) { + all_sink_pacs_read = true; + continue; + } else { + all_sink_pacs_read = false; + break; + } + } + + LOG(WARNING) << __func__ + << ": all_sink_pacs_read: " << all_sink_pacs_read; + if (all_sink_pacs_read) + dev->chars_read |= SINK_PAC; + + } else if (srcinfo != nullptr) { + srcinfo->read_src_pac_record = true; + bool all_source_pacs_read = false; + for (auto it = dev->src_info.begin(); + it != dev->src_info.end(); it ++) { + if (it->read_src_pac_record == true) { + all_source_pacs_read = true; + continue; + } else { + all_source_pacs_read = false; + break; + } + } + + LOG(WARNING) << __func__ + << ": all_source_pacs_read: " << all_source_pacs_read; + if (all_source_pacs_read) + dev->chars_read |= SRC_PAC; + } + } + + void OnReadOnlyPropertiesRead(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t *value, void* data) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << "unknown conn_id=" << loghex(conn_id); + return; + } + SinkPacsData* sinkinfo = FindSinkByHandle(dev, handle); + SrcPacsData* srcinfo = FindSrcByHandle(dev, handle); + + if (sinkinfo != nullptr || srcinfo != nullptr) { + ParsePacRecord(dev, handle, len, value, data); + + } else if (dev->sink_loc_handle == handle) { + uint8_t *p = value; + STREAM_TO_UINT32(dev->sink_locations, p); + dev->chars_read |= SINK_LOC; + btif_bap_add_audio_loc(dev->address, CodecDirection::CODEC_DIR_SINK, + dev->sink_locations); + LOG(WARNING) << __func__ << ": sink loc: " << loghex(dev->sink_locations); + + } else if(dev->src_loc_handle == handle) { + uint8_t *p = value; + STREAM_TO_UINT32(dev->src_locations, p); + dev->chars_read |= SRC_LOC; + btif_bap_add_audio_loc(dev->address, CodecDirection::CODEC_DIR_SRC, + dev->src_locations); + LOG(WARNING) << __func__ << ": src loc: " << loghex(dev->src_locations); + + } else if(dev->avail_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->available_contexts, p); + dev->chars_read |= AVAIL_CONTEXTS; + + } else if(dev->supp_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->supported_contexts, p); + dev->chars_read |= SUPP_CONTEXTS; + btif_bap_add_supp_contexts(dev->address, dev->supported_contexts); + } + + LOG(WARNING) << __func__ << ": chars_read: " << loghex(dev->chars_read); + + // check if all pacs characteristics are read + // send out the callback as service discovery completed + if (dev->chars_read == dev->chars_to_be_read) { + + UpdateConsolidatedsinkPacRecords(dev); + UpdateConsolidatedsrcPacRecords(dev); + + // get the callback and update the upper layers + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + dev->discovery_completed = true; + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_SUCCESS, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + } + } + + static void OnReadOnlyPropertiesReadStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadOnlyPropertiesRead(client_id, conn_id, status, handle, + len, value, data); + } + + static void OnReadAvailableAudioStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadAvailableAudio(client_id, conn_id, status, handle, + len, value, data); + } + + + private: + uint8_t gatt_client_id = BTA_GATTS_INVALID_IF; + uint16_t pacs_client_id = 0; + PacsDevices pacsDevices; + // client id to callbacks mapping + std::map callbacks; + + void find_server_changed_ccc_handle(uint16_t conn_id, + const gatt::Service* service) { + PacsDevice* pacsDevice = pacsDevices.FindByConnId(conn_id); + if (!pacsDevice) { + LOG(ERROR) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD)) { + pacsDevice->srv_changed_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + if (!pacsDevice->srv_changed_ccc_handle) { + LOG(ERROR) << __func__ + << ": cannot find service changed CCC descriptor"; + continue; + } + LOG(INFO) << __func__ << ": service_changed_ccc=" + << loghex(pacsDevice->srv_changed_ccc_handle); + break; + } + } + } + + // Find the handle for the client characteristics configuration of a given + // characteristics + uint16_t find_ccc_handle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + + if (!p_char) { + LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + + return 0; + } + + SinkPacsData *FindSinkByHandle(PacsDevice *dev, uint16_t handle) { + LOG(INFO) << __func__ << ": handle:" << loghex(handle); + auto iter = std::find_if(dev->sink_info.begin(), + dev->sink_info.end(), + [&handle](SinkPacsData data) { + return (data.sink_pac_handle == handle); + }); + + return (iter == dev->sink_info.end()) ? nullptr : &(*iter); + } + + SrcPacsData *FindSrcByHandle(PacsDevice *dev, uint16_t handle) { + LOG(INFO) << __func__ << ": handle:" << loghex(handle); + auto iter = std::find_if(dev->src_info.begin(), + dev->src_info.end(), + [&handle](SrcPacsData data) { + return (data.src_pac_handle == handle); + }); + + return (iter == dev->src_info.end()) ? nullptr : &(*iter); + } + + void UpdateConsolidatedsinkPacRecords(PacsDevice *dev) { + LOG(INFO) << __func__; + for (auto it = dev->sink_info.begin(); + it != dev->sink_info.end(); it ++) { + for (auto i = it->sink_pac_records.begin(); + i != it->sink_pac_records.end(); i ++) { + dev->consolidated_sink_pac_records. + push_back(static_cast(*i)); + } + } + } + + void UpdateConsolidatedsrcPacRecords(PacsDevice *dev) { + LOG(INFO) << __func__; + for (auto it = dev->src_info.begin(); + it != dev->src_info.end(); it ++) { + for (auto i = it->src_pac_records.begin(); + i != it->src_pac_records.end(); i ++) { + dev->consolidated_src_pac_records. + push_back(static_cast(*i)); + } + } + } +}; + +const char* get_gatt_event_name(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BTA_GATTC_DEREG_EVT) + CASE_RETURN_STR(BTA_GATTC_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_CLOSE_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_CMPL_EVT) + CASE_RETURN_STR(BTA_GATTC_NOTIF_EVT) + CASE_RETURN_STR(BTA_GATTC_ENC_CMPL_CB_EVT) + CASE_RETURN_STR(BTA_GATTC_CONN_UPDATE_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_CHG_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_DISC_DONE_EVT) + CASE_RETURN_STR(BTA_GATTC_CONGEST_EVT) + default: + return "Unknown Event"; + } +} + +void pacs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + if (p_data == nullptr || !instance) { + LOG(ERROR) << __func__ << ": p_data is null or no instance, return"; + return; + } + LOG(INFO) << __func__ << ": Event : " << get_gatt_event_name(event); + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_OPEN_EVT: { + tBTA_GATTC_OPEN& o = p_data->open; + instance->OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + break; + } + + case BTA_GATTC_CLOSE_EVT: { + tBTA_GATTC_CLOSE& c = p_data->close; + instance->OnGattDisconnected(c.status, c.conn_id, c.client_if, + c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: + if (!p_data->notify.is_notify || p_data->notify.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << p_data->notify.is_notify + << ", len=" << p_data->notify.len; + break; + } + instance->OnNotificationEvent(p_data->notify.conn_id, + p_data->notify.handle, p_data->notify.len, + p_data->notify.value); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_CONN_UPDATE_EVT: + instance->OnConnectionUpdateComplete(p_data->conn_update.conn_id, + p_data); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + instance->OnServiceDiscDoneEvent(p_data->remote_bda); + break; + case BTA_GATTC_CONGEST_EVT: + instance->OnCongestionEvent(p_data->congest.conn_id, + p_data->congest.congested); + break; + default: + break; + } +} + +void encryption_callback(const RawAddress* address, tGATT_TRANSPORT, void*, + tBTM_STATUS status) { + if (instance) { + instance->OnEncryptionComplete(*address, + status == BTM_SUCCESS ? true : false); + } +} + +void PacsClient::Initialize(PacsClientCallbacks* callbacks) { + if (instance) { + instance->Register(callbacks); + } else { + instance = new PacsClientImpl(); + instance->Register(callbacks); + } +} + +void PacsClient::CleanUp(uint16_t client_id) { + if(instance->GetClientCount()) { + instance->Deregister(client_id); + if(!instance->GetClientCount()) { + delete instance; + instance = nullptr; + } + } +} + +PacsClient* PacsClient::Get() { + CHECK(instance); + return instance; +} + +} // namespace pacs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/ucast_client_int.h b/le_audio/system/bt/bta/bap/ucast_client_int.h new file mode 100644 index 0000000000000000000000000000000000000000..2d535fa2a887d470065a3301b62c62977b638d3d --- /dev/null +++ b/le_audio/system/bt/bta/bap/ucast_client_int.h @@ -0,0 +1,1276 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#pragma once + +#include +#include "state_machine.h" +#include +#include "bta_bap_uclient_api.h" +#include "bta_pacs_client_api.h" +#include "bta_ascs_client_api.h" +#include "bt_trace.h" +#include "uclient_alarm.h" +#include "btif_util.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::ascs::GattState; +using bluetooth::bap::ascs::AscsClient; +using bluetooth::bap::ascs::AseParams; +using bluetooth::bap::ascs::AseCodecConfigParams; +using bluetooth::bap::ascs::AseCodecConfigOp; +using bluetooth::bap::cis::CisInterface; + +using bluetooth::bap::cis::CigState; +using bluetooth::bap::cis::CisState; + +using bluetooth::bap::alarm::BapAlarm; +using bluetooth::bap::alarm::BapAlarmCallbacks; + +class UstreamManager; +class UstreamManagers; +struct UcastAudioStream; +class UcastAudioStreams; +class StreamContexts; +struct StreamContext; +class StreamTracker; + +enum class StreamAttachedState { + IDLE = 0x1 << 0, + IDLE_TO_PHY = 0x1 << 1, + VIRTUAL = 0x1 << 2, + VIR_TO_PHY = 0x1 << 3, + PHYSICAL = 0x1 << 4 +}; + +enum class StreamControlType { + None = 0x00, + Connect = 0X01, + Disconnect = 0x02, + Start = 0x04, + Stop = 0x08, + Reconfig = 0x10, + UpdateStream = 0x20 +}; + +enum class DeviceType { + NONE = 0x00, + EARBUD = 0X01, // group member + HEADSET_STEREO = 0x02, // headset with 1 CIS + HEADSET_SPLIT_STEREO = 0x03 // headset with 2 CIS +}; + +enum class IntConnectState { + IDLE = 0x00, + PACS_CONNECTING = 0x01, + PACS_DISCOVERING = 0X02, + ASCS_CONNECTING = 0x03, + ASCS_DISCOVERING = 0x04, + ASCS_DISCOVERED = 0x05, +}; + +enum class AscsPendingCmd { + NONE = 0x00, + CODEC_CONFIG_ISSUED = 0x01, + QOS_CONFIG_ISSUED = 0x02, + ENABLE_ISSUED = 0x03, + START_READY_ISSUED = 0x04, + DISABLE_ISSUED = 0x05, + STOP_READY_ISSUED = 0x06, + RELEASE_ISSUED = 0x07, + UPDATE_METADATA_ISSUED = 0x08 +}; + +enum class CisPendingCmd { + NONE = 0x00, + CIG_CREATE_ISSUED = 0x08, + CIS_CREATE_ISSUED = 0x09, + CIS_SETUP_DATAPATH_ISSUED = 0x10, + CIS_RMV_DATAPATH_ISSUED = 0x11, + CIS_DESTROY_ISSUED = 0x12, + CIG_REMOVE_ISSUED = 0x13 +}; + +enum class GattPendingCmd { + NONE = 0x00, + GATT_CONN_PENDING = 0x01, + GATT_DISC_PENDING = 0x02 +}; + +typedef enum { + BAP_CONNECT_REQ_EVT = 0X00, + BAP_DISCONNECT_REQ_EVT, + BAP_START_REQ_EVT, + BAP_STOP_REQ_EVT, + BAP_RECONFIG_REQ_EVT, + BAP_STREAM_UPDATE_REQ_EVT, + PACS_CONNECTION_STATE_EVT, + PACS_DISCOVERY_RES_EVT, + PACS_AUDIO_CONTEXT_RES_EVT, + ASCS_CONNECTION_STATE_EVT, + ASCS_DISCOVERY_RES_EVT, + ASCS_ASE_STATE_EVT, + ASCS_ASE_OP_FAILED_EVT, + CIS_GROUP_STATE_EVT, + CIS_STATE_EVT, + BAP_TIME_OUT_EVT, +} BapEvent; + +struct BapConnect { + std::vector bd_addr; + bool is_direct; + std::vector streams; +}; + +struct BapDisconnect { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapStart { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapStop { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapReconfig { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapStreamUpdate { + RawAddress bd_addr; + std::vector update_streams; +}; + +struct PacsConnectionState { + RawAddress bd_addr; + ConnectionState state; +}; + +struct AscsConnectionState { + RawAddress bd_addr; + GattState state; +}; + +struct AscsDiscovery { + int status; + RawAddress bd_addr; + std::vector sink_ases_list; + std::vector src_ases_list; +}; + +struct AscsState { + RawAddress bd_addr; + AseParams ase_params; +}; + +struct AscsOpFailed { + RawAddress bd_addr; + ascs::AseOpId ase_op_id; + std::vector ase_list; +}; + +struct CisGroupState { + uint8_t cig_id; + CigState state; +}; + +struct CisStreamState { + uint8_t cig_id; + uint8_t cis_id; + uint8_t direction; + CisState state; +}; + +struct PacsDiscovery { + int status; + RawAddress bd_addr; + std::vector sink_pac_records; + std::vector src_pac_records; + uint32_t sink_locations; + uint32_t src_locations; + uint32_t available_contexts; + uint32_t supported_contexts; +}; + +struct PacsAvailableContexts { + RawAddress bd_addr; + uint32_t available_contexts; +}; + +struct IntStrmTracker { + IntStrmTracker(StreamType strm_type, uint8_t ase_id, uint8_t cig_id, + uint8_t cis_id, CodecConfig &codec_config, + QosConfig &qos_config) + : strm_type(strm_type), ase_id(ase_id) , cig_id(cig_id) , + cis_id(cis_id), codec_config(codec_config), + qos_config(qos_config) { + attached_state = StreamAttachedState::IDLE; + } + StreamType strm_type; + uint8_t ase_id; + uint8_t cig_id; + uint8_t cis_id; + CodecConfig codec_config; + QosConfig qos_config; + StreamAttachedState attached_state; +}; + +class IntStrmTrackers { + public: + std::vector FindByCigId(uint8_t cig_id) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->cig_id == cig_id) { + LOG(WARNING) << __func__ << " tracker found"; + trackers.push_back(*i); + } + } + return trackers; + } + + std::vector FindByCigIdAndDir(uint8_t cig_id, + uint8_t direction) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->cig_id == cig_id && + (*i)->strm_type.direction == direction) { + trackers.push_back(*i); + } + } + return trackers; + } + + std::vector FindByCisId(uint8_t cig_id, uint8_t cis_id) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->cig_id == cig_id && (*i)->cis_id == cis_id) { + trackers.push_back(*i); + } + } + return trackers; + } + + IntStrmTracker *FindByIndex(uint8_t i) { + IntStrmTracker *tracker = int_strm_trackers.at(i); + return tracker; + } + + IntStrmTracker *FindByAseId(uint8_t ase_id) { + auto iter = std::find_if(int_strm_trackers.begin(), int_strm_trackers.end(), + [&ase_id](IntStrmTracker *tracker) { + return tracker->ase_id == ase_id; + }); + + return (iter == int_strm_trackers.end()) ? nullptr : (*iter); + } + + IntStrmTracker *FindOrAddBytrackerType(StreamType strm_type, + uint8_t ase_id, uint8_t cig_id, uint8_t cis_id, + CodecConfig &codec_config, QosConfig &qos_config) { + + auto iter = std::find_if(int_strm_trackers.begin(), int_strm_trackers.end(), + [&strm_type, &cig_id, &cis_id](IntStrmTracker *tracker) { + return ((tracker->strm_type.type == strm_type.type) && + (tracker->strm_type.direction == + strm_type.direction) && + (tracker->cig_id == cig_id) && + (tracker->cis_id == cis_id)); + }); + + if (iter == int_strm_trackers.end()) { + IntStrmTracker *tracker = new IntStrmTracker(strm_type, + ase_id, cig_id, cis_id, codec_config, qos_config); + int_strm_trackers.push_back(tracker); + return tracker; + } else { + return (*iter); + } + } + + void Remove(StreamType strm_type, uint8_t cig_id, uint8_t cis_id) { + for (auto it = int_strm_trackers.begin(); it != int_strm_trackers.end();) { + if (((*it)->strm_type.type = strm_type.type) && + ((*it)->strm_type.direction = strm_type.direction) && + ((*it)->cig_id = cig_id) && ((*it)->cis_id = cis_id)) { + delete(*it); + it = int_strm_trackers.erase(it); + } else { + it++; + } + } + } + + void RemoveVirtualAttachedTrackers() { + LOG(WARNING) << __func__; + for (auto it = int_strm_trackers.begin(); it != int_strm_trackers.end();) { + if ((*it)->attached_state == StreamAttachedState::VIRTUAL) { + delete(*it); + it = int_strm_trackers.erase(it); + LOG(WARNING) << __func__ + << ": Removed virtual attached tracker"; + } else { + it++; + } + } + } + + size_t size() { return (int_strm_trackers.size()); } + + std::vector *GetTrackerList() { + return &int_strm_trackers; + } + + std::vector GetTrackerListByDir(uint8_t direction) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->strm_type.direction == direction) { + trackers.push_back(*i); + } + } + return trackers; + } + + private: + std::vector int_strm_trackers; +}; + +union BapEventData { + BapConnect connect_req; + BapDisconnect disc_req; + BapStart start_req; + BapStop stop_req; + BapReconfig reconfig_req; + PacsConnectionState connection_state_rsp; + PacsDiscovery pacs_discovery_rsp; + PacsAvailableContexts pacs_audio_context_rsp; +}; + +enum class TimeoutVal { //in milli seconds(1sec = 1000ms) + ConnectingTimeout = 10000, + StartingTimeout = 2000, + StoppingTimeout = 2000, + DisconnectingTimeout = 1000, + ReconfiguringTimeout = 2000, + UpdatingTimeout = 1000 +}; + +enum class MaxTimeoutVal { //in milli seconds(1sec = 1000ms) + ConnectingTimeout = 10000, + StartingTimeout = 4000, + StoppingTimeout = 4000, + DisconnectingTimeout = 4000, + ReconfiguringTimeout = 8000, + UpdatingTimeout = 4000 +}; + +enum class TimeoutReason { + STATE_TRANSITION = 1, +}; + +struct BapTimeout { + RawAddress bd_addr; + StreamTracker* tracker; + TimeoutReason reason; + int transition_state; +}; + +class StreamTracker : public bluetooth::common::StateMachine { + public: + enum { + kStateIdle, // + kStateConnecting, // + kStateConnected, // + kStateStarting, // + kStateStreaming, // + kStateStopping, // + kStateDisconnecting, // + kStateReconfiguring, // + kStateUpdating + }; + + class StateIdle : public State { + public: + StateIdle(StreamTracker& sm) + : State(sm, kStateIdle), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Idle"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + }; + + class StateConnecting : public State { + public: + StateConnecting(StreamTracker& sm) + : State(sm, kStateConnecting), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Connecting"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + void DeriveDeviceType(PacsDiscovery *pacs_discovery); + bool AttachStreamsToContext(std::vector *all_trackers, + std::vector *streams, + uint8_t cis_count, + std::vector *ase_ops); + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + PacsDiscovery pacs_discovery_; + AscsDiscovery ascs_discovery_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateConnected : public State { + public: + StateConnected(StreamTracker& sm) + : State(sm, kStateConnected), tracker_(sm), + strm_mgr_(sm.GetStreamManager()){} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Connected"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + }; + + class StateStarting : public State { + public: + StateStarting(StreamTracker& sm) + : State(sm, kStateStarting), tracker_(sm), + strm_mgr_(sm.GetStreamManager()){} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Starting"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + bool CheckAndUpdateStreamingState(); + alarm_t* state_transition_timer; + PacsAvailableContexts pacs_contexts; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateStreaming : public State { + public: + StateStreaming(StreamTracker& sm) + : State(sm, kStateStreaming), tracker_(sm), + strm_mgr_(sm.GetStreamManager()){} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Streaming"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + }; + + class StateStopping : public State { + public: + StateStopping(StreamTracker& sm) + : State(sm, kStateStopping), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Stopping"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + bool TerminateCisAndCig(UcastAudioStream *stream); + bool CheckAndUpdateStoppedState(); + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateDisconnecting : public State { + public: + StateDisconnecting(StreamTracker& sm) + : State(sm, kStateDisconnecting), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Disconnecting"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + bool TerminateGattConnection(); + void ContinueDisconnection(UcastAudioStream *stream); + bool CheckAndUpdateDisconnectedState(); + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateReconfiguring: public State { + public: + StateReconfiguring(StreamTracker& sm) + : State(sm, kStateReconfiguring), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Reconfiguring"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateUpdating: public State { + public: + StateUpdating(StreamTracker& sm) + : State(sm, kStateUpdating), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Updating"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + StreamTracker(int init_state_id, UstreamManager *strm_mgr, + std::vector *connect_streams, + std::vector *reconfig_streams, + std::vector *streams, + StreamControlType ops_type): + init_state_id_(init_state_id), + strm_mgr_(strm_mgr) { + + state_idle_ = new StateIdle(*this); + state_connecting_ = new StateConnecting(*this); + state_connected_ = new StateConnected(*this); + state_starting_ = new StateStarting(*this); + state_streaming_ = new StateStreaming(*this); + state_stopping_ = new StateStopping(*this); + state_disconnecting_ = new StateDisconnecting(*this); + state_reconfiguring_ = new StateReconfiguring(*this); + state_updating_ = new StateUpdating(*this); + pacs_disc_succeded_ = false; + + AddState(state_idle_); + AddState(state_connecting_); + AddState(state_connected_); + AddState(state_starting_); + AddState(state_streaming_); + AddState(state_stopping_); + AddState(state_disconnecting_); + AddState(state_reconfiguring_); + AddState(state_updating_); + + switch(init_state_id) { + case kStateIdle: + SetInitialState(state_idle_); + break; + case kStateConnected: + SetInitialState(state_connected_); + break; + case kStateStreaming: + SetInitialState(state_streaming_); + break; + case kStateDisconnecting: + SetInitialState(state_disconnecting_); + break; + default: + SetInitialState(state_idle_); + } + + str_ops_type = ops_type; + + if(ops_type == StreamControlType::Connect) { + conn_streams = *connect_streams; + } else if(ops_type == StreamControlType::Reconfig) { + reconf_streams = *reconfig_streams; + } else if(ops_type != StreamControlType::UpdateStream) { + other_streams = *streams; + } + } + + void PauseRemoteDevInteraction(bool pause); + bool decoupleStream(StreamType *stream_info); + + uint8_t ChooseBestCodec(StreamType stream_type, + std::vector *codec_qos_configs, + PacsDiscovery *pacs_discovery); + + bool ChooseBestQos(QosConfig *src_config, + ascs::AseCodecConfigParams *rem_qos_prefs, + QosConfig *dst_config, + int stream_state, uint8_t stream_direction); + + bool HandlePacsConnectionEvent(void *p_data); + + bool HandlePacsAudioContextEvent(PacsAvailableContexts *pacs_contexts); + + bool HandleCisEventsInStreaming(void* p_data); + + bool HandleStreamUpdate (int cur_state); + + bool CheckAndUpdateStreamingState(IntStrmTrackers *int_strm_trackers); + + bool HandleAscsConnectionEvent(void *p_data); + + void HandleCigStateEvent(uint32_t event, void *p_data, + IntStrmTrackers *int_strm_trackers); + + void HandleAseStateEvent(void *p_data, StreamControlType control_type, + IntStrmTrackers *int_strm_trackers); + + void HandleAseOpFailedEvent(void *p_data); + + bool ValidateAseUpdate(void* p_data, IntStrmTrackers *int_strm_trackers, + int exp_strm_state); + + bool HandleDisconnect(void* p_data, int cur_state); + + bool HandleRemoteDisconnect(uint32_t event, void* p_data, int cur_state); + + bool StreamCanbeDisconnected(StreamContext *cur_context, uint8_t ase_id); + + bool HandleInternalDisconnect(bool release); + + bool HandleStop(void* p_data, int cur_state); + + bool HandleRemoteStop(uint32_t event, void* p_data, int cur_state); + + bool HandleRemoteReconfig(uint32_t event, void* p_data, int cur_state); + + bool PrepareCodecConfigPayload(std::vector *ase_ops, + UcastAudioStream *stream); + + void CheckAndSendQosConfig(IntStrmTrackers *int_strm_trackers); + + void CheckAndSendEnable(IntStrmTrackers *int_strm_trackers); + + bool HandleAbruptStop(uint32_t event, void* p_data); + + alarm_t* SetTimer(const char* alarmname, BapTimeout* timeout, + TimeoutReason reason, uint64_t ms); + + void ClearTimer(alarm_t* timer, const char* alarmname); + + void OnTimeout(void* data); + + StreamControlType GetControlType() { + return str_ops_type; + } + + UstreamManager *GetStreamManager() { + return strm_mgr_; + } + + bool UpdatePacsDiscovery(PacsDiscovery disc_res) { + pacs_disc_succeded_ = true; + pacs_discovery_ = disc_res; + return true; + } + + PacsDiscovery *GetPacsDiscovery() { + if(pacs_disc_succeded_) { + return &pacs_discovery_; + } else { + return nullptr; + } + } + + bool UpdateControlType(StreamControlType ops_type) { + str_ops_type = ops_type; + return true; + } + + bool UpdateStreams(std::vector *streams) { + other_streams = *streams; + return true; + } + + bool UpdateConnStreams( + std::vector *connect_streams) { + conn_streams = *connect_streams; + return true; + } + + bool UpdateReconfStreams( + std::vector *reconfig_streams) { + reconf_streams = *reconfig_streams; + return true; + } + + bool UpdateMetaUpdateStreams( + std::vector *meta_streams) { + meta_update_streams = *meta_streams; + return true; + } + + std::vector *GetStreams() { + return &other_streams; + } + std::vector *GetConnStreams() { + return &conn_streams; + } + std::vector *GetReconfStreams() { + return &reconf_streams; + } + + std::vector *GetMetaUpdateStreams() { + return &meta_update_streams; + } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BAP_CONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_DISCONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_START_REQ_EVT) + CASE_RETURN_STR(BAP_STOP_REQ_EVT) + CASE_RETURN_STR(BAP_RECONFIG_REQ_EVT) + CASE_RETURN_STR(BAP_STREAM_UPDATE_REQ_EVT) + CASE_RETURN_STR(PACS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(PACS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(PACS_AUDIO_CONTEXT_RES_EVT) + CASE_RETURN_STR(ASCS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(ASCS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(ASCS_ASE_STATE_EVT) + CASE_RETURN_STR(ASCS_ASE_OP_FAILED_EVT) + CASE_RETURN_STR(CIS_GROUP_STATE_EVT) + CASE_RETURN_STR(CIS_STATE_EVT) + CASE_RETURN_STR(BAP_TIME_OUT_EVT) + default: + return "Unknown Event"; + } + } + + private: + int init_state_id_; + UstreamManager *strm_mgr_; + std::vector conn_streams; + std::vector other_streams; + std::vector reconf_streams; + std::vector meta_update_streams; + StreamControlType str_ops_type; + bool pacs_disc_succeded_; + PacsDiscovery pacs_discovery_; + StateIdle *state_idle_; + StateConnecting *state_connecting_; + StateConnected *state_connected_; + StateStarting *state_starting_; + StateStreaming *state_streaming_; + StateStopping *state_stopping_; + StateDisconnecting *state_disconnecting_; + StateReconfiguring *state_reconfiguring_; + StateUpdating *state_updating_; +}; + +struct StreamOpConnect { + StreamType stream_type; + //std::vector codec_configs; + //std::vector qos_configs; +}; + +struct StreamOpReconfig { + StreamType stream_type; + //std::vector codec_configs; + //std::vector qos_configs; +}; + +union StreamOpData { + StreamOpConnect connect_op; + StreamType stream_type; + StreamOpReconfig reconfig_op; +}; + +struct StreamOpNode { + bool busy; + bool is_client_originated; + StreamControlType ops_type; + StreamOpData ops_data; +}; + +struct StreamIdType { + uint8_t ase_id; + uint8_t ase_direction; + bool virtual_attach; + uint8_t cig_id; + uint8_t cis_id; +}; + +struct StreamContext { + StreamContext(StreamType strm_type) + : stream_type(strm_type) { + stream_state = StreamState::DISCONNECTED; + attached_state = StreamAttachedState::IDLE; + } + StreamType stream_type; + StreamAttachedState attached_state; + std::vector stream_ids; + StreamState stream_state; + IntConnectState connection_state; + CodecConfig codec_config; + QosConfig qos_config; + QosConfig req_qos_config; +}; + +class StreamContexts { + public: + + StreamContext *FindByType(StreamType stream_type); + + StreamContext *FindOrAddByType(StreamType stream_type); + + void Remove(StreamType stream_type); + + bool IsAseAttached(StreamType stream_type); + + std::vector FindByAseAttachedState(uint16_t ase_id, + StreamAttachedState state); + + + StreamContext* FindByAseId(uint16_t ase_id); + + std::vector *GetAllContexts() { + return &strm_contexts; + } + + size_t size() { return (strm_contexts.size()); } + + private: + std::vector strm_contexts; +}; + + +class StreamOpsQueue { + public: + bool Add(StreamOpNode op_node); + + bool AddFirst(StreamOpNode op_node); + + StreamOpNode *GetNextNode(); + + bool Remove(StreamType stream_type); + + StreamOpNode* FindByContext(StreamType stream_type); + + bool ChangeOpType(StreamType stream_type, StreamControlType new_ops_type); + + size_t size() { return (queue.size()); } + + std::vector queue; +}; + +class StreamTrackers { + public: + StreamTracker *FindOrAddByType(int init_state_id, UstreamManager *strm_mgr, + std::vector *connect_streams, + std::vector *reconfig_streams, + std::vector *streams, + StreamControlType ops_type); + + bool Remove(std::vector streams, + StreamControlType ops_type); + + void RemoveByStates(std::vector state_ids); + + std::map > GetTrackersByType( + std::vector *streams); + + StreamTracker *FindByStreamsType(std::vector *streams); + + std::vector GetTrackersByStates( + std::vector *state_ids); + + bool ChangeOpType( StreamType stream_type, + StreamControlType new_ops_type); + + bool IsStreamTrackerValid(StreamTracker* Tracker, + std::vector *state_ids); + + size_t size() { return (stream_trackers.size()); } + + std::vector stream_trackers; +}; + +struct UcastAudioStream { + UcastAudioStream(uint8_t ase_id, uint8_t ase_state, uint8_t ase_direction) + : ase_id(ase_id) , ase_state(ase_state) { + ase_state = ascs::ASE_STATE_INVALID; + cig_state = CigState::INVALID; + cis_state = CisState::INVALID; + direction = ase_direction; + cig_id = 0XFF; + cis_id = 0xFF; + cis_retry_count = 0; + overall_state = StreamTracker::kStateIdle; + ase_pending_cmd = AscsPendingCmd::NONE; + cis_pending_cmd = CisPendingCmd::NONE; + } + bool is_active; + uint8_t ase_id; + uint8_t ase_state; + AseParams ase_params; + AseCodecConfigParams pref_qos_params; + uint8_t cig_id; + CigState cig_state; + uint8_t cis_id; + CisState cis_state; + uint8_t cis_retry_count; + int overall_state; // stream tracker state + StreamControlType control_type; + AscsPendingCmd ase_pending_cmd; + CisPendingCmd cis_pending_cmd; + uint16_t audio_context; + uint8_t direction; + uint32_t audio_location; + CodecConfig codec_config; + QosConfig qos_config; + QosConfig req_qos_config; +}; + +class UcastAudioStreams { + public: + + std::vector FindByCigId(uint8_t cig_id, int state) { + std::vector streams; + for (auto i = audio_streams.begin(); i != audio_streams.end();i++) { + if((*i)->cig_id == cig_id && + (*i)->overall_state == state) { + streams.push_back(*i); + } + } + return streams; + } + + std::vector FindByCisId(uint8_t cig_id, uint8_t cis_id) { + std::vector streams; + for (auto i = audio_streams.begin(); i != audio_streams.end();i++) { + if((*i)->cig_id == cig_id && (*i)->cis_id == cis_id) { + streams.push_back(*i); + } + } + return streams; + } + + UcastAudioStream *FindByStreamType(uint16_t audio_context, + uint8_t direction) { + auto it = audio_streams.begin(); + while (it != audio_streams.end()) { + if((*it)->audio_context == audio_context && + (*it)->direction & direction) { + break; + } + it++; + } + return (it == audio_streams.end()) ? nullptr : (*it); + } + + UcastAudioStream *FindByCisIdAndDir(uint8_t cig_id, uint8_t cis_id, + uint8_t dir) { + auto it = audio_streams.begin(); + while (it != audio_streams.end()) { + if((*it)->cig_id == cig_id && (*it)->cis_id == cis_id && + (*it)->direction & dir) { + break; + } + it++; + } + return (it == audio_streams.end()) ? nullptr : (*it); + } + + UcastAudioStream *FindByAseId(uint8_t ase_id) { + auto it = audio_streams.begin(); + while (it != audio_streams.end()) { + if((*it)->ase_id == ase_id) { + break; + } + it++; + } + return (it == audio_streams.end()) ? nullptr : (*it); + } + + UcastAudioStream *FindOrAddByAseId(uint8_t ase_id, uint8_t ase_state, + uint8_t ase_direction) { + auto iter = std::find_if(audio_streams.begin(), audio_streams.end(), + [&ase_id, &ase_direction](UcastAudioStream *stream) { + return (stream->ase_id == ase_id && + stream->direction == ase_direction); + }); + + if (iter == audio_streams.end()) { + UcastAudioStream *stream = new UcastAudioStream(ase_id, ase_state, + ase_direction); + stream->overall_state = StreamTracker::kStateIdle; + audio_streams.push_back(stream); + auto it = std::find_if(audio_streams.begin(), audio_streams.end(), + [&ase_id, &ase_direction](UcastAudioStream* stream) { + return (stream->ase_id == ase_id && + stream->direction == ase_direction); + }); + return (it == audio_streams.end()) ? nullptr : (*it); + } else { + return (*iter); + } + } + + std::vector GetStreamsByStates( + std::vector state_ids, + uint8_t directions) { + std::vector streams; + for (auto i = audio_streams.begin(); i != audio_streams.end();i++) { + for (auto j = state_ids.begin(); j != state_ids.end();j++) { + if(((*i)->overall_state == *j) && ((*i)->direction & directions)) { + streams.push_back(*i); + } + } + } + return streams; + } + + void Remove(uint8_t ase_id) { + for (auto it = audio_streams.begin(); it != audio_streams.end();) { + if ((*it)->ase_id == ase_id) { + delete(*it); + it = audio_streams.erase(it); + } else { + it++; + } + } + } + + std::vector *GetAllStreams() { + return &audio_streams; + } + + size_t size() { return (audio_streams.size()); } + // UcastAudioStream + private: + std::vector audio_streams; +}; + +struct GattPendingData { + GattPendingData() { + ascs_pending_cmd = GattPendingCmd::NONE; + pacs_pending_cmd = GattPendingCmd::NONE; + } + GattPendingCmd ascs_pending_cmd; + GattPendingCmd pacs_pending_cmd; +}; + +class UstreamManager { + public: + UstreamManager(const RawAddress& address, PacsClient *pacs_client, + uint16_t pacs_client_id, + AscsClient *ascs_client, CisInterface *cis_intf, + UcastClientCallbacks* callbacks, + BapAlarm *bap_alarm) + : address(address) , pacs_client(pacs_client), + pacs_client_id(pacs_client_id), + ascs_client(ascs_client), cis_intf(cis_intf), + ucl_callbacks(callbacks), bap_alarm(bap_alarm) { + pacs_state = ConnectionState::DISCONNECTED; + gatt_pending_data.pacs_pending_cmd = GattPendingCmd::NONE; + gatt_pending_data.ascs_pending_cmd = GattPendingCmd::NONE; + ascs_state = GattState::DISCONNECTED; + dev_type = DeviceType::NONE; + } + + bool PushEventToTracker(uint32_t event, void *p_data, + std::vector *state_ids); + + std::map > SplitContextOnState( + std::vector *streams); + + void ProcessEvent(uint32_t event, void* p_data); + + uint16_t GetConnId(); + + std::list GetCigId(); + + std::list GetCisId(); + + void ReportStreamState (std::vector stream_info); + + RawAddress &GetAddress() { return address; }; + + PacsClient *GetPacsClient() { + return pacs_client; + } + + uint16_t GetPacsClientId() { + return pacs_client_id; + } + + GattPendingData *GetGattPendingData() { + return &gatt_pending_data; + } + + bool UpdatePacsState(ConnectionState state) { + pacs_state = state; + return true; + } + + bool UpdateAscsState(GattState state) { + ascs_state = state; + return true; + } + + ConnectionState GetPacsState() { + return pacs_state; + } + + GattState GetAscsState() { + return ascs_state; + } + + AscsClient *GetAscsClient() { + return ascs_client; + } + + CisInterface *GetCisInterface() { + return cis_intf; + } + + BapAlarm *GetBapAlarm() { + return bap_alarm; + } + + UcastAudioStreams *GetAudioStreams() { + return &audio_streams; + } + + StreamTrackers *GetStreamTrackers() { + return &stream_trackers; + } + + StreamContexts *GetStreamContexts() { + return &stream_contexts; + } + + UcastClientCallbacks *GetUclientCbacks() { + return ucl_callbacks; + } + + void UpdateDevType(DeviceType device_type) { + dev_type = device_type; + } + + DeviceType GetDevType() { + return dev_type; + } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BAP_CONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_DISCONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_START_REQ_EVT) + CASE_RETURN_STR(BAP_STOP_REQ_EVT) + CASE_RETURN_STR(BAP_RECONFIG_REQ_EVT) + CASE_RETURN_STR(BAP_STREAM_UPDATE_REQ_EVT) + CASE_RETURN_STR(PACS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(PACS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(PACS_AUDIO_CONTEXT_RES_EVT) + CASE_RETURN_STR(ASCS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(ASCS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(ASCS_ASE_STATE_EVT) + CASE_RETURN_STR(ASCS_ASE_OP_FAILED_EVT) + CASE_RETURN_STR(CIS_GROUP_STATE_EVT) + CASE_RETURN_STR(CIS_STATE_EVT) + CASE_RETURN_STR(BAP_TIME_OUT_EVT) + default: + return "Unknown Event"; + } + } + + private: + RawAddress address; + PacsClient *pacs_client; + uint16_t pacs_client_id; + AscsClient *ascs_client; + ConnectionState pacs_state; + CisInterface *cis_intf; + GattState ascs_state; + StreamOpsQueue ops_queue; + UcastAudioStreams audio_streams; + StreamTrackers stream_trackers; + StreamContexts stream_contexts; + GattPendingData gatt_pending_data; + UcastClientCallbacks* ucl_callbacks; + BapAlarm *bap_alarm; + DeviceType dev_type; +}; + +class UstreamManagers { + public: + UstreamManager* FindByAddress(const RawAddress& address); + + UstreamManager* FindorAddByAddress(const RawAddress& address, + PacsClient *pacs_client, uint16_t pacs_client_id, + AscsClient *ascs_client, CisInterface *cis_intf, + UcastClientCallbacks* callbacks, BapAlarm* bap_alarm); + + + std::vector *GetAllManagers(); + + void Remove(const RawAddress& address); + + size_t size() { return (strm_mgrs.size()); } + + std::vector strm_mgrs; +}; + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_alarm.cc b/le_audio/system/bt/bta/bap/uclient_alarm.cc new file mode 100644 index 0000000000000000000000000000000000000000..cca6efac3f4fb2920a4f9b3ed7d99f3f2e59c05c --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_alarm.cc @@ -0,0 +1,99 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "uclient_alarm.h" +#include "bt_trace.h" +#define LOG_TAG "uclient_alarm" + +namespace bluetooth { +namespace bap { +namespace alarm { + +class BapAlarmImpl; +BapAlarmImpl *instance; + +static void alarm_handler(void* data); + +class BapAlarmImpl : public BapAlarm { + public: + BapAlarmImpl(BapAlarmCallbacks* callback): + callbacks(callback) { } + + ~BapAlarmImpl() override = default; + + void CleanUp () { } + + alarm_t* Create(const char* name) { + return alarm_new(name); + } + + void Delete(alarm_t* alarm) { + alarm_free(alarm); + } + + void Start(alarm_t* alarm, period_ms_t interval_ms, + void* data) { + alarm_set_on_mloop(alarm, interval_ms, alarm_handler, data); + } + + void Stop(alarm_t* alarm) { + alarm_cancel(alarm); + } + + bool IsScheduled(const alarm_t* alarm) { + return alarm_is_scheduled(alarm); + } + + void Timeout(void* data) { + if (callbacks) + callbacks->OnTimeout(data); // Call uclient_main + } + + private: + BapAlarmCallbacks *callbacks; +}; + +void BapAlarm::Initialize( + BapAlarmCallbacks* callbacks) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + } else { + instance = new BapAlarmImpl(callbacks); + } +} + +void BapAlarm::CleanUp() { + BapAlarmImpl* ptr = instance; + instance = nullptr; + ptr->CleanUp(); + delete ptr; +} + +BapAlarm* BapAlarm::Get() { + return instance; +} + +static void alarm_handler(void* data) { + if (instance) + instance->Timeout(data); +} + +} //alarm +} //bap +} //bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_alarm.h b/le_audio/system/bt/bta/bap/uclient_alarm.h new file mode 100644 index 0000000000000000000000000000000000000000..3e03d94b52336ba3bbb05e15139f8ac3efdd2f0e --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_alarm.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#pragma once + +#include +#include +#include "osi/include/alarm.h" +#include + +namespace bluetooth { +namespace bap { +namespace alarm { + +class BapAlarmCallbacks { + public: + virtual ~BapAlarmCallbacks() = default; + + /** Callback for timer timeout */ + virtual void OnTimeout(void* data) = 0; +}; + +class BapAlarm { + public: + virtual ~BapAlarm() = default; + + static void Initialize(BapAlarmCallbacks* callbacks); + static void CleanUp(); + static BapAlarm* Get(); + + virtual alarm_t* Create(const char* name) = 0; + + virtual void Delete(alarm_t* alarm) = 0; + + virtual void Start(alarm_t* alarm, period_ms_t interval_ms, + void* data) = 0; + + virtual void Stop(alarm_t* alarm) = 0; + + virtual bool IsScheduled(const alarm_t* alarm) = 0; + + virtual void Timeout(void* data) = 0; +}; + +} //alarm +} //bap +} //bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_main.cc b/le_audio/system/bt/bta/bap/uclient_main.cc new file mode 100644 index 0000000000000000000000000000000000000000..0df1c39f6c77045c7676e2395b1889674ae8a51e --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_main.cc @@ -0,0 +1,548 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "bta_bap_uclient_api.h" +#include "ucast_client_int.h" +#include "bta_pacs_client_api.h" +#include "bta_ascs_client_api.h" +#include +#include +#include +#include +#include "bta_closure_api.h" +#include "bt_trace.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using base::Bind; +using base::Unretained; +using base::Closure; +using bluetooth::Uuid; + +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::PacsClientCallbacks; + +using bluetooth::bap::ascs::AscsClient; +using bluetooth::bap::ascs::GattState; +using bluetooth::bap::ascs::AscsClientCallbacks; +using bluetooth::bap::ascs::AseOpId; +using bluetooth::bap::ascs::AseOpStatus; +using bluetooth::bap::ascs::AseParams; + +using bluetooth::bap::ucast::UstreamManagers; +using bluetooth::bap::ucast::UstreamManager; + +using bluetooth::bap::ucast::BapEventData; +using bluetooth::bap::ucast::BapEvent; +using bluetooth::bap::ucast::BapConnect; +using bluetooth::bap::ucast::BapDisconnect; +using bluetooth::bap::ucast::BapStart; +using bluetooth::bap::ucast::BapStop; +using bluetooth::bap::ucast::BapReconfig; +using bluetooth::bap::ucast::PacsConnectionState; +using bluetooth::bap::ucast::PacsDiscovery; +using bluetooth::bap::ucast::PacsAvailableContexts; + +using bluetooth::bap::ucast::CisGroupState; +using bluetooth::bap::ucast::CisStreamState; +using bluetooth::bap::cis::CigState; +using bluetooth::bap::cis::CisState; +using bluetooth::bap::cis::CisInterface; + +using bluetooth::bap::alarm::BapAlarm; +using bluetooth::bap::alarm::BapAlarmCallbacks; + +class UcastClientImpl; +UcastClientImpl* instance = nullptr; + +class CisInterfaceCallbacksImpl : public CisInterfaceCallbacks { + public: + ~CisInterfaceCallbacksImpl() = default; + /** Callback for connection state change */ + void OnCigState(uint8_t cig_id, CigState state) { + do_in_bta_thread(FROM_HERE, Bind(&CisInterfaceCallbacks::OnCigState, + Unretained(UcastClient::Get()), cig_id, + state)); + + } + + void OnCisState(uint8_t cig_id, uint8_t cis_id, + uint8_t direction, CisState state) { + do_in_bta_thread(FROM_HERE, Bind(&CisInterfaceCallbacks::OnCisState, + Unretained(UcastClient::Get()), cig_id, + cis_id, direction, state)); + } +}; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, int client_id) override { + LOG(WARNING) << __func__ << ": status =" << loghex(status); + do_in_bta_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnInitialized, + Unretained(UcastClient::Get()), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::pacs::ConnectionState state) override { + LOG(WARNING) << __func__ << ": address=" << address; + do_in_bta_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnConnectionState, + Unretained(UcastClient::Get()), + address, state)); + } + + void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) override { + do_in_bta_thread(FROM_HERE, + Bind(&PacsClientCallbacks::OnAudioContextAvailable, + Unretained(UcastClient::Get()), + address, available_contexts)); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnSearchComplete, + Unretained(UcastClient::Get()), + status, address, + sink_pac_records, + src_pac_records, + sink_locations, + src_locations, + available_contexts, + supported_contexts)); + } +}; + +class AscsClientCallbacksImpl : public AscsClientCallbacks { + public: + ~AscsClientCallbacksImpl() = default; + void OnAscsInitialized(int status, int client_id) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnAscsInitialized, + Unretained(UcastClient::Get()), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::ascs::GattState state) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_bta_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnConnectionState, + Unretained(UcastClient::Get()), + address, state)); + } + + void OnAseOpFailed(const RawAddress& address, + AseOpId ase_op_id, + std::vector status) { + do_in_bta_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseOpFailed, + Unretained(UcastClient::Get()), + address, ase_op_id, status)); + + } + + void OnAseState(const RawAddress& address, + AseParams ase) override { + do_in_bta_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseState, + Unretained(UcastClient::Get()), + address, ase)); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnSearchComplete, + Unretained(UcastClient::Get()), + status, address, sink_ase_list, + src_ase_list)); + } +}; + +class BapAlarmCallbacksImpl : public BapAlarmCallbacks { + public: + ~BapAlarmCallbacksImpl() = default; + /** Callback for timer timeout */ + void OnTimeout(void* data) { + do_in_bta_thread(FROM_HERE, Bind(&BapAlarmCallbacks::OnTimeout, + Unretained(UcastClient::Get()), data)); + } +}; + +class UcastClientImpl : public UcastClient { + public: + ~UcastClientImpl() override = default; + + // APIs exposed for upper layers + void Connect(std::vector address, bool is_direct, + std::vector streams) override { + if(address.size() == 1) { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address[0], + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + BapConnect data = { .bd_addr = address, .is_direct = is_direct, + .streams = streams}; + mgr->ProcessEvent(BAP_CONNECT_REQ_EVT, &data); + } + } + + void Disconnect(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapDisconnect data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_DISCONNECT_REQ_EVT, &data); + } + + void Start(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapStart data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_START_REQ_EVT, &data); + } + + void Stop(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapStop data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_STOP_REQ_EVT, &data); + + } + + void Reconfigure(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapReconfig data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_RECONFIG_REQ_EVT, &data); + } + + void UpdateStream(const RawAddress& address, + std::vector update_streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapStreamUpdate data = { .bd_addr = address, + .update_streams = update_streams}; + mgr->ProcessEvent(BAP_STREAM_UPDATE_REQ_EVT, &data); + } + + // To be called from device specific stream manager + bool ReportStreamState(const RawAddress& address) { + //TODO to check + return true; + + } + + // PACS client related callbacks + // to be forwarded to device specific stream manager + void OnInitialized(int status, int client_id) override { + LOG(WARNING) << __func__ << ": actual client_id = " << loghex(client_id); + pacs_client_id = client_id; + } + + void OnConnectionState(const RawAddress& address, + ConnectionState state) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + PacsConnectionState data = { .bd_addr = address, + .state = state + }; + mgr->ProcessEvent(PACS_CONNECTION_STATE_EVT, &data); + } + + void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + PacsAvailableContexts data = { + .bd_addr = address, + .available_contexts = available_contexts, + }; + mgr->ProcessEvent(PACS_AUDIO_CONTEXT_RES_EVT, &data); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + PacsDiscovery data = { + .status = status, + .bd_addr = address, + .sink_pac_records = sink_pac_records, + .src_pac_records = src_pac_records, + .sink_locations = sink_locations, + .src_locations = src_locations, + .available_contexts = available_contexts, + .supported_contexts = supported_contexts + }; + mgr->ProcessEvent(PACS_DISCOVERY_RES_EVT, &data); + } + + // ASCS client related callbacks + // to be forwarded to device specific stream manager + void OnAscsInitialized(int status, int client_id) override { + + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::ascs::GattState state) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsConnectionState data = { .bd_addr = address, + .state = state + }; + mgr->ProcessEvent(ASCS_CONNECTION_STATE_EVT, &data); + } + + void OnAseOpFailed(const RawAddress& address, + AseOpId ase_op_id, + std::vector status) { + + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsOpFailed data = { + .bd_addr = address, + .ase_op_id = ase_op_id, + .ase_list = status + }; + mgr->ProcessEvent(ASCS_ASE_OP_FAILED_EVT, &data); + } + + void OnAseState(const RawAddress& address, + AseParams ase_params) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsState data = { + .bd_addr = address, + .ase_params = ase_params + }; + mgr->ProcessEvent(ASCS_ASE_STATE_EVT, &data); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsDiscovery data = { + .status = status, + .bd_addr = address, + .sink_ases_list = sink_ase_list, + .src_ases_list = src_ase_list + }; + mgr->ProcessEvent(ASCS_DISCOVERY_RES_EVT, &data); + } + + // cis callbacks + void OnCigState(uint8_t cig_id, CigState state) override { + std::vector *mgrs_list = strm_mgrs.GetAllManagers(); + // hand over the request to stream manager + CisGroupState data = { + .cig_id = cig_id, + .state = state + }; + + for (auto it = mgrs_list->begin(); it != mgrs_list->end(); it++) { + (*it)->ProcessEvent(CIS_GROUP_STATE_EVT, &data); + } + } + + void OnCisState(uint8_t cig_id, uint8_t cis_id, uint8_t direction, + CisState state) override { + std::vector *mgrs_list = strm_mgrs.GetAllManagers(); + // hand over the request to stream manager + CisStreamState data = { + .cig_id = cig_id, + .cis_id = cis_id, + .direction = direction, + .state = state + }; + + for (auto it = mgrs_list->begin(); it != mgrs_list->end(); it++) { + (*it)->ProcessEvent(CIS_STATE_EVT, &data); + } + } + + void OnTimeout(void* data) override { + LOG(ERROR) << __func__; + BapTimeout* data_ = (BapTimeout *)data; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(data_->bd_addr, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + mgr->ProcessEvent(BAP_TIME_OUT_EVT, data); + } + + bool Init(UcastClientCallbacks *callback) { + // register callbacks with CIS, ASCS client, PACS client + pacs_callbacks = new PacsClientCallbacksImpl; + PacsClient::Initialize(pacs_callbacks); + pacs_client = PacsClient::Get(); + + ascs_callbacks = new AscsClientCallbacksImpl; + AscsClient::Init(ascs_callbacks); + ascs_client = AscsClient::Get(); + + cis_callbacks = new CisInterfaceCallbacksImpl; + CisInterface::Initialize(cis_callbacks); + cis_intf = CisInterface::Get(); + + bap_alarm_cb = new BapAlarmCallbacksImpl; + BapAlarm::Initialize(bap_alarm_cb); + bap_alarm = BapAlarm::Get(); + + pacs_client_id = 0; + if(ucl_callbacks != nullptr) { + // flag an error + return false; + } else { + ucl_callbacks = callback; + return true; + } + } + + bool CleanUp() { + if(ucl_callbacks != nullptr) { + ucl_callbacks = nullptr; + //call clean ups for each clients(ascs, pacs, cis and bap_alarm) + LOG(ERROR) << __func__ + <<": Cleaning up pacs, ascs clients, cis intf and bap_alarm."; + pacs_client->CleanUp(pacs_client_id); + ascs_client->CleanUp(0x01); + cis_intf->CleanUp(); + bap_alarm->CleanUp(); + pacs_client = nullptr; + ascs_client = nullptr; + cis_intf = nullptr; + bap_alarm = nullptr; + // remove all stream managers and other clean ups + return true; + } else { + return false; + } + } + + private: + UcastClientCallbacks* ucl_callbacks; + UstreamManagers strm_mgrs; + PacsClient *pacs_client; + AscsClient *ascs_client; + PacsClientCallbacks *pacs_callbacks; + AscsClientCallbacks *ascs_callbacks; + CisInterface *cis_intf; + CisInterfaceCallbacks *cis_callbacks; + uint16_t pacs_client_id; + BapAlarm* bap_alarm; + BapAlarmCallbacks* bap_alarm_cb; +}; + +void UcastClient::Initialize(UcastClientCallbacks* callbacks) { + if (!instance) { + instance = new UcastClientImpl(); + instance->Init(callbacks); + } else { + LOG(ERROR) << __func__ << " 2nd client registration ignored"; + } +} + +void UcastClient::CleanUp() { + if(instance && instance->CleanUp()) { + delete instance; + instance = nullptr; + } +} + +UcastClient* UcastClient::Get() { + CHECK(instance); + return instance; +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc b/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc new file mode 100644 index 0000000000000000000000000000000000000000..ea069379ad7825f4862320542682f353189c538d --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc @@ -0,0 +1,1013 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "bta_bap_uclient_api.h" +#include "ucast_client_int.h" +#include "bt_trace.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using namespace std; +using bluetooth::bap::ucast::UstreamManagers; +using bluetooth::bap::ucast::UstreamManager; + +std::map state_map = { + {StreamState::DISCONNECTED, StreamTracker::kStateIdle} , + {StreamState::CONNECTING, StreamTracker::kStateConnecting}, + {StreamState::CONNECTED, StreamTracker::kStateConnected}, + {StreamState::STARTING, StreamTracker::kStateStarting}, + {StreamState::STREAMING, StreamTracker::kStateStreaming}, + {StreamState::STOPPING, StreamTracker::kStateStopping}, + {StreamState::DISCONNECTING, StreamTracker::kStateDisconnecting}, + {StreamState::RECONFIGURING, StreamTracker::kStateReconfiguring} +}; + +StreamContext *StreamContexts::FindByType(StreamType stream_type) { + auto iter = std::find_if(strm_contexts.begin(), strm_contexts.end(), + [&stream_type](StreamContext *context) { + return ((context->stream_type.type == stream_type.type) + && (context->stream_type.direction == + stream_type.direction)); + }); + + if (iter == strm_contexts.end()) { + return nullptr; + } else { + return (*iter); + } +} + +std::vector StreamContexts::FindByAseAttachedState( + uint16_t ase_id, StreamAttachedState state) { + std::vector contexts_list; + for(auto i = strm_contexts.begin(); i != strm_contexts.end();i++) { + if(static_cast((*i)->attached_state) & + static_cast(state)) { + for(auto j = (*i)->stream_ids.begin(); j != (*i)->stream_ids.end();j++) { + if(j->ase_id == ase_id) { + contexts_list.push_back(*i); + break; + } + } + } + } + return contexts_list; +} + +StreamContext *StreamContexts::FindOrAddByType(StreamType stream_type) { + auto iter = std::find_if(strm_contexts.begin(), strm_contexts.end(), + [&stream_type](StreamContext *context) { + return ((context->stream_type.type == + stream_type.type) && + (context->stream_type.direction == + stream_type.direction)); + }); + + if (iter == strm_contexts.end()) { + StreamContext *context = new StreamContext(stream_type); + strm_contexts.push_back(context); + return context; + } else { + return (*iter); + } +} + +void StreamContexts::Remove(StreamType stream_type) { + for (auto it = strm_contexts.begin(); it != strm_contexts.end();) { + if (((*it)->stream_type.type = stream_type.type) && + ((*it)->stream_type.direction = stream_type.direction)) { + delete(*it); + it = strm_contexts.erase(it); + } else { + it++; + } + } +} + +bool StreamContexts::IsAseAttached(StreamType stream_type) { + return false; + +} + +StreamContext* StreamContexts::FindByAseId(uint16_t ase_id) { + auto iter = std::find_if(strm_contexts.begin(), strm_contexts.end(), + [&ase_id](StreamContext *context) { + auto it = std::find_if(context->stream_ids.begin(), + context->stream_ids.end(), + [&ase_id](StreamIdType id) { + return (id.ase_id == ase_id); + }); + if (it != context->stream_ids.end()) { + return true; + } else return false; + + }); + + if (iter == strm_contexts.end()) { + return nullptr; + } else { + return (*iter); + } +} + +StreamTracker* StreamTrackers::FindOrAddByType(int init_state_id, + UstreamManager *strm_mgr, + std::vector *connect_streams, + std::vector *reconfig_streams, + std::vector *streams, + StreamControlType ops_type) { + bool found = false; + auto iter = stream_trackers.begin(); + while (iter != stream_trackers.end() && !found) { + if((*iter)->GetControlType() == ops_type) { + if(ops_type == StreamControlType::Connect) { + // compare connection streams + std::vector *conn_strms = (*iter)->GetConnStreams(); + if(conn_strms->size() == connect_streams->size()) { + uint8_t len = connect_streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamConnect src = conn_strms->at(i); + StreamConnect dst = connect_streams->at(i); + if((src.stream_type.type == dst.stream_type.type) && + (src.stream_type.direction == dst.stream_type.direction)) { + LOG(WARNING) << __func__ << " StreamConnect found"; + found = true; + break; + } + } + } + } else if(ops_type == StreamControlType::Reconfig) { + // compare connection streams + std::vector *reconf_strms = (*iter)->GetReconfStreams(); + if(reconf_strms->size() == reconfig_streams->size()) { + uint8_t len = reconfig_streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamReconfig src = reconf_strms->at(i); + StreamReconfig dst = reconfig_streams->at(i); + if((src.stream_type.type == dst.stream_type.type) && + (src.stream_type.direction == dst.stream_type.direction)) { + LOG(WARNING) << __func__ << " StreamReconfig found"; + found = true; + break; + } + } + } + } else if(ops_type != StreamControlType::None && + ops_type != StreamControlType::UpdateStream) { + // compare connection streams + std::vector *strms = (*iter)->GetStreams(); + if(strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamType src = strms->at(i); + StreamType dst = streams->at(i); + if((src.type == dst.type) && + (src.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamType found"; + found = true; + break; + } + } + } + } + } + iter++; + } + + if (iter == stream_trackers.end()) { + StreamTracker *tracker = new StreamTracker(init_state_id, strm_mgr, + connect_streams, reconfig_streams, + streams, ops_type); + stream_trackers.push_back(tracker); + return tracker; + } else { + return (*iter); + } +} + +bool StreamTrackers::Remove(std::vector streams, + StreamControlType ops_type) { + return true; +} + +std::vector StreamTrackers::GetTrackersByStates( + std::vector *state_ids) { + vector trackers; + for (auto i = stream_trackers.begin(); + i != stream_trackers.end();i++) { + for (auto j = state_ids->begin(); j != state_ids->end();j++) { + if((*i)->StateId() == *j) { + LOG(WARNING) << __func__ << " tracker found"; + trackers.push_back(*i); + } + } + } + return trackers; +} + +void StreamTrackers::RemoveByStates(std::vector state_ids) { + for (auto i = stream_trackers.begin(); + i != stream_trackers.end();) { + bool found = false; + for (auto j = state_ids.begin(); j != state_ids.end();j++) { + if((*i)->StateId() == *j) { + LOG(WARNING) << __func__ << " tracker found"; + found = true; + break; + } + } + if(found) { + delete(*i); + i = stream_trackers.erase(i); + } else { + i++; + } + } +} + + +std::map > + StreamTrackers::GetTrackersByType( + std::vector *streams) { + std::vector req_types = *streams; + std::vector all_types; + std::map > tracker_and_type_map; + for (auto iter = stream_trackers.begin(); iter != stream_trackers.end(); + iter++) { + all_types.clear(); + if((*iter)->GetControlType() == StreamControlType::Connect) { + // compare connection streams + std::vector *conn_strms = (*iter)->GetConnStreams(); + uint8_t len = conn_strms->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamConnect src = conn_strms->at(i); + all_types.push_back(src.stream_type); + } + } else if((*iter)->GetControlType() == StreamControlType::Reconfig) { + // compare connection streams + std::vector *reconf_strms = (*iter)->GetReconfStreams(); + uint8_t len = reconf_strms->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamReconfig src = reconf_strms->at(i); + all_types.push_back(src.stream_type); + } + } else if((*iter)->GetControlType() == StreamControlType::UpdateStream) { + // compare connection streams + std::vector *update_streams = (*iter)->GetMetaUpdateStreams(); + uint8_t len = update_streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamUpdate src = update_streams->at(i); + all_types.push_back(src.stream_type); + } + } else if((*iter)->GetControlType() != StreamControlType::None) { + // compare connection streams + std::vector *strms = (*iter)->GetStreams(); + uint8_t len = strms->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamType src = strms->at(i); + all_types.push_back(src); + } + } + uint8_t count = 0; + std::vector filtered_types; + if(all_types.size() <= req_types.size()) { + filtered_types.clear(); + for (auto it = all_types.begin(); it != all_types.end(); it++) { + for (auto it_2 = req_types.begin(); it_2 != req_types.end();) { + if (((it_2)->type == it->type) && + ((it_2)->direction == it->direction)) { + filtered_types.push_back(*it_2); + tracker_and_type_map[*iter] = filtered_types; + it_2 = req_types.erase(it_2); + count++; + } else { + it_2++; + } + } + } + if(all_types.size() != count) { + LOG(ERROR) << __func__ << " invalid request"; + } + } else { + LOG(ERROR) << __func__ << " invalid request"; + } + } + if(req_types.size()) { + tracker_and_type_map[nullptr] = req_types; + } + return tracker_and_type_map; +} + + +StreamTracker *StreamTrackers::FindByStreamsType( + std::vector *streams) { + bool found = false; + auto iter = stream_trackers.begin(); + while (iter != stream_trackers.end()) { + if((*iter)->GetControlType() == StreamControlType::Connect) { + // compare connection streams + std::vector *conn_strms = (*iter)->GetConnStreams(); + if(conn_strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamConnect src = conn_strms->at(i); + StreamType dst = streams->at(i); + if((src.stream_type.type == dst.type) && + (src.stream_type.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamConnect found"; + found = true; + break; + } + } + } + } else if((*iter)->GetControlType() == StreamControlType::Reconfig) { + // compare connection streams + std::vector *reconf_strms = (*iter)->GetReconfStreams(); + if(reconf_strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamReconfig src = reconf_strms->at(i); + StreamType dst = streams->at(i); + if((src.stream_type.type == dst.type) && + (src.stream_type.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamReconfig found"; + found = true; + break; + } + } + } + } else if((*iter)->GetControlType() == StreamControlType::UpdateStream) { + // compare connection streams + std::vector *update_strms = (*iter)->GetMetaUpdateStreams(); + if(update_strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamUpdate src = update_strms->at(i); + StreamType dst = streams->at(i); + if((src.stream_type.type == dst.type) && + (src.stream_type.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamUpdate found"; + found = true; + break; + } + } + } + } else if((*iter)->GetControlType() != StreamControlType::None) { + // compare connection streams + std::vector *strms = (*iter)->GetStreams(); + if(strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamType src = strms->at(i); + StreamType dst = streams->at(i); + if((src.type == dst.type) && + (src.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamType found"; + found = true; + break; + } + } + } + } + if(found) break; + iter++; + } + + if (iter == stream_trackers.end()) { + return nullptr; + } else { + return (*iter); + } +} + +bool StreamTrackers::ChangeOpType( StreamType stream_type, + StreamControlType new_ops_type) { + return true; +} + +bool StreamTrackers::IsStreamTrackerValid(StreamTracker* tracker, + std::vector *state_ids) { + vector trackers_ = GetTrackersByStates(state_ids); + LOG(WARNING) << __func__; + if(trackers_.empty()) return false; + + for (auto it = trackers_.begin(); it != trackers_.end(); it++) { + if ((*it) == tracker) { + LOG(WARNING) << __func__ <<": Cached Tracker is valid"; + return true; + } + } + return false; +} + +bool UstreamManager::PushEventToTracker(uint32_t event, void *p_data, + std::vector *state_ids) { + vector trackers = stream_trackers.GetTrackersByStates( + state_ids); + if(trackers.empty()) return false; + + for (auto it = trackers.begin(); it != trackers.end(); it++) { + (*it)->ProcessEvent(event, p_data); + } + return true; +} + + +std::map > UstreamManager::SplitContextOnState( + std::vector *streams) { + StreamContexts *contexts = GetStreamContexts(); + std::vector req_types = *streams; + std::map > state_and_type_map; + + for (auto it = req_types.begin(); it != req_types.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if (context) { + int state = state_map[context->stream_state]; + state_and_type_map[state].push_back(*it); + it = req_types.erase(it); + } else { + it++; + } + } + return state_and_type_map; +} + +void UstreamManager::ProcessEvent(uint32_t event, void *p_data) { + LOG(WARNING) << __func__ <<": Event: " << GetEventName(event) + <<", bt_addr: " << GetAddress(); + + std::vector stable_state_ids = { + StreamTracker::kStateConnected, + StreamTracker::kStateStreaming, + StreamTracker::kStateIdle + }; + + std::vector transient_state_ids = { + StreamTracker::kStateConnecting, + StreamTracker::kStateReconfiguring, + StreamTracker::kStateDisconnecting, + StreamTracker::kStateStarting, + StreamTracker::kStateStopping, + StreamTracker::kStateUpdating, + }; + StreamContexts *contexts = GetStreamContexts(); + + switch (event) { + + case BAP_CONNECT_REQ_EVT: { + BapConnect *evt_data = (BapConnect *) p_data; + std::vector conn_streams = evt_data->streams; + LOG(WARNING) << __func__ << ": size: " << conn_streams.size(); + + for (auto it = conn_streams.begin(); it != conn_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if(context && context->stream_state != StreamState::DISCONNECTED) { + LOG(WARNING) << __func__ << ": Stream is not in disconnected state"; + it = conn_streams.erase(it); + } else { + it++; + } + } + + if(!conn_streams.size()) { + LOG(ERROR) << __func__ << ": All streams are not in disconnected state"; + break; + } + + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateIdle, + this, &conn_streams, nullptr, nullptr, + StreamControlType::Connect); + + if(tracker) { + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_DISCONNECT_REQ_EVT: { + BapDisconnect *evt_data = (BapDisconnect *) p_data; + std::vector disc_streams = evt_data->streams; + BapDisconnect int_evt_data; + + for (auto it = disc_streams.begin(); it != disc_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(!context || context->stream_state == StreamState::DISCONNECTED) { + LOG(WARNING) << __func__ << " Stream is already disconnected"; + it = disc_streams.erase(it); + } else { + it++; + } + } + + if(!disc_streams.size()) { + LOG(ERROR) << __func__ << " All streams are already disconnected"; + break; + } + + LOG(WARNING) << __func__ << " disc streams size " << disc_streams.size(); + + // validate the combinations media Tx or voice TX|RX + std::map > + tracker_and_type_list = + stream_trackers.GetTrackersByType(&disc_streams); + + + // check if all streams disconnection or subset of streams + // create the new tracker + for (auto itr = tracker_and_type_list.begin(); + itr != tracker_and_type_list.end(); itr++) { + if(itr->first == nullptr) { + std::map > + list = SplitContextOnState(&itr->second); + for (auto itr_2 = list.begin(); itr_2 != list.end(); itr_2++) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + itr_2->first, + this, nullptr, nullptr, &itr_2->second, + StreamControlType::Disconnect); + LOG(ERROR) << __func__ << " new tracker start "; + tracker->Start(); + int_evt_data.streams = itr_2->second; + tracker->ProcessEvent(event, &int_evt_data); + } + } else { + LOG(ERROR) << __func__ << " existing tracker start "; + StreamTracker *tracker = itr->first; + int_evt_data.streams = itr->second; + tracker->ProcessEvent(event, &int_evt_data); + } + } + } break; + + case BAP_START_REQ_EVT: { + BapStart *evt_data = (BapStart *) p_data; + std::vector start_streams = evt_data->streams; + LOG(WARNING) << __func__ << " start streams size " << start_streams.size(); + + for (auto it = start_streams.begin(); it != start_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(!context || context->stream_state != StreamState::CONNECTED) { + LOG(WARNING) << __func__ << " Stream is not in connected state"; + it = start_streams.erase(it); + } else { + it++; + } + } + + if(!start_streams.size()) { + LOG(WARNING) << __func__ << " Not eligible for stream start"; + break; + } + + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &start_streams); + // create new tracker + if(tracker == nullptr) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateConnected, + this, nullptr, nullptr, &start_streams, + StreamControlType::Start); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_STOP_REQ_EVT: { + BapStop *evt_data = (BapStop *) p_data; + std::vector stop_streams = evt_data->streams; + LOG(WARNING) << __func__ << " stop streams size " << stop_streams.size(); + + for (auto it = stop_streams.begin(); it != stop_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(!context || (context->stream_state != StreamState::STREAMING && + context->stream_state != StreamState::STARTING)) { + LOG(WARNING) << __func__ << " Stream is not in streaming state"; + it = stop_streams.erase(it); + } else { + it++; + } + } + + if(!stop_streams.size()) { + LOG(WARNING) << __func__ << " Not eligible for stream stop"; + break; + } + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &stop_streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateStreaming, + this, nullptr, nullptr, &stop_streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_STREAM_UPDATE_REQ_EVT: { + BapStreamUpdate *evt_data = (BapStreamUpdate *) p_data; + std::vector update_streams = evt_data->update_streams; + std::vector streams; + + for (auto it = update_streams.begin(); it != update_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if(!context || (context->stream_state != StreamState::STREAMING && + context->stream_state != StreamState::STARTING)) { + LOG(WARNING) << __func__ << " Stream is not in proper state"; + it = update_streams.erase(it); + } else { + it++; + } + } + + if(!update_streams.size()) { + LOG(WARNING) << __func__ << " All streams are not in proper state"; + break; + } + + for (auto it = update_streams.begin(); + it != update_streams.end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + + LOG(WARNING) << __func__ << " update streams size " << streams.size(); + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateStreaming, + this, nullptr, nullptr, nullptr, + StreamControlType::UpdateStream); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_RECONFIG_REQ_EVT: { + BapReconfig *evt_data = (BapReconfig *) p_data; + std::vector reconf_streams = evt_data->streams; + std::vector streams; + + for (auto it = reconf_streams.begin(); it != reconf_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if(!context || context->stream_state != StreamState::CONNECTED) { + LOG(WARNING) << __func__ << " Stream is not in Connected state"; + it = reconf_streams.erase(it); + } else { + it++; + } + } + + if(!reconf_streams.size()) { + LOG(WARNING) << __func__ << " All streams are not connected"; + break; + } + + for (auto it = reconf_streams.begin(); + it != reconf_streams.end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + + LOG(WARNING) << __func__ << " reconf streams size " << streams.size(); + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateConnected, + this, nullptr, &reconf_streams, nullptr, + StreamControlType::Reconfig); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case PACS_CONNECTION_STATE_EVT: { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + UpdatePacsState(pacs_state->state); + int state = StreamTracker::kStateIdle; + if (pacs_state->state != ConnectionState::DISCONNECTED) { + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } else { + + std::vector *context_list = contexts->GetAllContexts(); + std::vector streams; + + for (auto it = context_list->begin(); it != context_list->end(); it++) { + if((*it)->stream_state != StreamState::DISCONNECTED) { + streams.push_back((*it)->stream_type); + state = state_map[(*it)->stream_state]; + } + } + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Disconnect); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } + } else { + + std::vector *context_list = contexts->GetAllContexts(); + std::vector disc_streams; + + for (auto it = context_list->begin(); it != context_list->end(); it++) { + if((*it)->stream_state != StreamState::DISCONNECTED) { + disc_streams.push_back((*it)->stream_type); + } + } + + LOG(WARNING) << __func__ << ": size: " << disc_streams.size(); + + // validate the combinations media Tx or voice TX|RX + std::map > + tracker_and_type_list = + stream_trackers.GetTrackersByType(&disc_streams); + + // check if all streams disconnection or subset of streams + // create the new tracker + for (auto itr = tracker_and_type_list.begin(); + itr != tracker_and_type_list.end(); itr++) { + if(itr->first == nullptr) { + std::map > + list = SplitContextOnState(&itr->second); + for (auto itr_2 = list.begin(); itr_2 != list.end(); itr_2++) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + itr_2->first, + this, nullptr, nullptr, &itr_2->second, + StreamControlType::Disconnect); + LOG(ERROR) << __func__ << " new tracker start "; + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } else { + LOG(ERROR) << __func__ << " existing tracker start "; + StreamTracker *tracker = itr->first; + tracker->ProcessEvent(event, p_data); + } + } + } + } break; + + case PACS_AUDIO_CONTEXT_RES_EVT: { + // update the same event to upper layers also + PacsAvailableContexts *contexts = (PacsAvailableContexts *) p_data; + ucl_callbacks->OnStreamAvailable( + address, + (contexts->available_contexts >> 16), + (contexts->available_contexts & 0xFFFF)); + std::vector state_ids = { StreamTracker::kStateStarting, + StreamTracker::kStateUpdating}; + PushEventToTracker(event, p_data, &state_ids); + } break; + + case PACS_DISCOVERY_RES_EVT: + case ASCS_DISCOVERY_RES_EVT: { + // validate the combinations media Tx or voice TX|RX + std::vector state_ids = { StreamTracker::kStateConnecting, + StreamTracker::kStateReconfiguring }; + PushEventToTracker(event, p_data, &state_ids); + } break; + + case ASCS_CONNECTION_STATE_EVT: { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + UpdateAscsState(ascs_state->state); + PushEventToTracker(event, p_data, &transient_state_ids); + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } + } break; + + case ASCS_ASE_STATE_EVT: { + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } + // create strm trackers based on ase ID and push it to + // newly created tracker. + // TODO Handle Releasing , disabling, codec configured states + // initiated from remote device + + AscsState *ascs = ((AscsState *) p_data); + UcastAudioStream *audio_stream = nullptr; + // find out the stream for the given ase id + uint8_t ase_id = ascs->ase_params.ase_id; + std::vector streams; + int state = StreamTracker::kStateIdle; + + UcastAudioStreams *audio_strms = GetAudioStreams(); + audio_stream = audio_strms->FindByAseId(ase_id); + if(audio_stream) { + StreamType type = { + .type = audio_stream->audio_context, + .direction = audio_stream->direction + }; + streams.push_back(type); + state = audio_stream->overall_state; + } + + LOG(WARNING) << __func__ << " size " << streams.size(); + + if (ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Disconnect); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else if (ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else if (ascs->ase_params.ase_state == + ascs::ASE_STATE_CODEC_CONFIGURED) { + // TODO reconfig from remote device + } else if (ascs->ase_params.ase_state == + ascs::ASE_STATE_QOS_CONFIGURED) { + // this can happen when CIS is lost and detected on remote side + // first so it will immediately transition to QOS configured. + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } break; + + case CIS_GROUP_STATE_EVT: { + // validate the combinations media Tx or voice TX|RX + std::vector state_ids = { StreamTracker::kStateStarting, + StreamTracker::kStateStopping, + StreamTracker::kStateDisconnecting }; + PushEventToTracker(event, p_data, &state_ids); + } break; + + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } + + // find out the stream for the given ase id + int state = StreamTracker::kStateIdle; + std::vector streams; + + UcastAudioStreams *audio_strms = GetAudioStreams(); + std::vector cis_streams = audio_strms->FindByCisId( + data->cig_id, data->cis_id); + + + if(cis_streams.empty()) break; + + for (auto it = cis_streams.begin(); it != cis_streams.end(); it++) { + StreamType type = { + .type = (*it)->audio_context, + .direction = (*it)->direction + }; + streams.push_back(type); + state = (*it)->overall_state; + } + + LOG(WARNING) << __func__ << " size " << streams.size(); + + // create strm trackers based on CIS ID and push it to + // newly created tracker. + // CIS disconnected + if(data->state == CisState::READY) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_TIME_OUT_EVT: { + BapTimeout* evt_data = (BapTimeout*) p_data; + + //Get Cached tracker and check if it is valid + if (stream_trackers.IsStreamTrackerValid(evt_data->tracker, + &transient_state_ids)) { + PushEventToTracker(event, p_data, &transient_state_ids); + } else { + LOG(WARNING) << __func__ << ": Tracker is not valid "; + } + } break; + + default: + break; + } + + // look for all stream trackers which are moved to stable states + // like connected, streaming, idle, and destroy those trackers here + stream_trackers.RemoveByStates(stable_state_ids); +} + +// TODO to introduce a queue for serializing the Audio stream establishment +UstreamManager* UstreamManagers::FindByAddress(const RawAddress& address) { + auto iter = std::find_if(strm_mgrs.begin(), strm_mgrs.end(), + [&address](UstreamManager *strm_mgr) { + return strm_mgr->GetAddress() == address; + }); + + return (iter == strm_mgrs.end()) ? nullptr : (*iter); +} + +UstreamManager* UstreamManagers::FindorAddByAddress(const RawAddress& address, + PacsClient *pacs_client, uint16_t pacs_client_id, + AscsClient *ascs_client, CisInterface *cis_intf, + UcastClientCallbacks* ucl_callbacks, + BapAlarm *bap_alarm) { + auto iter = std::find_if(strm_mgrs.begin(), strm_mgrs.end(), + [&address](UstreamManager *strm_mgr) { + return strm_mgr->GetAddress() == address; + }); + + if (iter == strm_mgrs.end()) { + UstreamManager *strm_mgr = new UstreamManager(address, pacs_client, + pacs_client_id, ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + strm_mgrs.push_back(strm_mgr); + return strm_mgr; + } else { + return (*iter); + } +} + +std::vector *UstreamManagers::GetAllManagers() { + return &strm_mgrs; + +} + +void UstreamManagers::Remove(const RawAddress& address) { + for (auto it = strm_mgrs.begin(); it != strm_mgrs.end();) { + if ((*it)->GetAddress() == address) { + delete(*it); + it = strm_mgrs.erase(it); + } else { + it++; + } + } +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc b/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc new file mode 100644 index 0000000000000000000000000000000000000000..3272e5633668f1816ea2ec19592f2e5086ef900c --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc @@ -0,0 +1,4001 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "bta_bap_uclient_api.h" +#include "ucast_client_int.h" +#include "bt_trace.h" +#include "btif/include/btif_bap_codec_utils.h" +#include "osi/include/properties.h" +#include "uclient_alarm.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecBPS; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecChannelMode; +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::cis::CisInterface; +using bluetooth::bap::ascs::AseCodecConfigOp; +using bluetooth::bap::ascs::AseQosConfigOp; +using bluetooth::bap::ascs::AseEnableOp; +using bluetooth::bap::ascs::AseStartReadyOp; +using bluetooth::bap::ascs::AseStopReadyOp; +using bluetooth::bap::ascs::AseDisableOp; +using bluetooth::bap::ascs::AseReleaseOp; +using bluetooth::bap::ascs::AseUpdateMetadataOp; + +using cis::IsoHciStatus; +using bluetooth::bap::alarm::BapAlarm; + +using bluetooth::bap::ucast::CONTENT_TYPE_MEDIA; +using bluetooth::bap::ucast::CONTENT_TYPE_CONVERSATIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_UNSPECIFIED; +using bluetooth::bap::ucast::CONTENT_TYPE_GAME; + +constexpr uint8_t LTV_TYPE_SAMP_FREQ = 0X01; +constexpr uint8_t LTV_TYPE_FRAME_DUR = 0x02; +constexpr uint8_t LTV_TYPE_CHNL_ALLOC = 0x03; +constexpr uint8_t LTV_TYPE_OCTS_PER_FRAME = 0X04; +constexpr uint8_t LTV_TYPE_FRAMES_PER_SDU = 0X05; +constexpr uint8_t LTV_TYPE_STRM_AUDIO_CONTEXTS = 0x02; + +constexpr uint8_t LTV_LEN_SAMP_FREQ = 0X02; +constexpr uint8_t LTV_LEN_FRAME_DUR = 0x02; +constexpr uint8_t LTV_LEN_CHNL_ALLOC = 0x05; +constexpr uint8_t LTV_LEN_OCTS_PER_FRAME = 0X03; +constexpr uint8_t LTV_LEN_FRAMES_PER_SDU = 0X02; +constexpr uint8_t LTV_LEN_STRM_AUDIO_CONTEXTS = 0x03; + +constexpr uint8_t LTV_VAL_SAMP_FREQ_8K = 0X01; +//constexpr uint8_t LTV_VAL_SAMP_FREQ_11K = 0X02; +constexpr uint8_t LTV_VAL_SAMP_FREQ_16K = 0X03; +//constexpr uint8_t LTV_VAL_SAMP_FREQ_22K = 0X04; +constexpr uint8_t LTV_VAL_SAMP_FREQ_24K = 0X05; +constexpr uint8_t LTV_VAL_SAMP_FREQ_32K = 0X06; +constexpr uint8_t LTV_VAL_SAMP_FREQ_441K = 0X07; +constexpr uint8_t LTV_VAL_SAMP_FREQ_48K = 0X08; +constexpr uint8_t LTV_VAL_SAMP_FREQ_882K = 0X09; +constexpr uint8_t LTV_VAL_SAMP_FREQ_96K = 0X0A; +constexpr uint8_t LTV_VAL_SAMP_FREQ_176K = 0X0B; +constexpr uint8_t LTV_VAL_SAMP_FREQ_192K = 0X0C; +//constexpr uint8_t LTV_VAL_SAMP_FREQ_384K = 0X0D; + +constexpr uint8_t LC3_CODEC_ID = 0x06; +constexpr uint8_t ASCS_CLIENT_ID = 0x01; + +constexpr uint8_t TGT_LOW_LATENCY = 0x01; +constexpr uint8_t TGT_BAL_LATENCY = 0x02; +constexpr uint8_t TGT_HIGH_RELIABLE = 0x03; + +std::map freq_to_ltv_map = { + {CodecSampleRate::CODEC_SAMPLE_RATE_8000, LTV_VAL_SAMP_FREQ_8K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_16000, LTV_VAL_SAMP_FREQ_16K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_24000, LTV_VAL_SAMP_FREQ_24K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_32000, LTV_VAL_SAMP_FREQ_32K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_44100, LTV_VAL_SAMP_FREQ_441K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_48000, LTV_VAL_SAMP_FREQ_48K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_88200, LTV_VAL_SAMP_FREQ_882K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_96000, LTV_VAL_SAMP_FREQ_96K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_176400, LTV_VAL_SAMP_FREQ_176K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_192000, LTV_VAL_SAMP_FREQ_192K } +}; + +std::list directions = { + cis::DIR_FROM_AIR, + cis::DIR_TO_AIR +}; + +std::vector locations = { + AUDIO_LOC_LEFT, + AUDIO_LOC_RIGHT +}; + +// common functions used from Stream tracker state Handlers +uint8_t StreamTracker::ChooseBestCodec(StreamType stream_type, + std::vector *codec_qos_configs, + PacsDiscovery *pacs_discovery) { + bool codec_found = false; + uint8_t index = 0; + // check the stream direction, based on direction look for + // matching record from preferred list of upper layer and + // remote device sink or src pac records + std::vector *pac_records = nullptr; + if(stream_type.direction == ASE_DIRECTION_SINK) { + pac_records = &pacs_discovery->sink_pac_records; + } else if(stream_type.direction == ASE_DIRECTION_SRC) { + pac_records = &pacs_discovery->src_pac_records; + } + + if (!pac_records) { + LOG(ERROR) << __func__ << "pac_records is null"; + return 0xFF; + } + DeviceType dev_type = strm_mgr_->GetDevType(); + for (auto i = codec_qos_configs->begin(); i != codec_qos_configs->end() + ; i++, index++) { + if(dev_type == DeviceType::EARBUD || + dev_type == DeviceType::HEADSET_STEREO) { + if((*i).qos_config.ascs_configs.size() != 1) continue; + } else if(dev_type == DeviceType::HEADSET_SPLIT_STEREO) { + if((*i).qos_config.ascs_configs.size() != 2) continue; + } + for (auto j = pac_records->begin(); + j != pac_records->end();j++) { + CodecConfig *src = &((*i).codec_config); + CodecConfig *dst = &(*j); + if (IsCodecConfigEqual(src,dst)) { + LOG(WARNING) << __func__ << ": Checking for matching Codec"; + if (GetLc3QPreference(src) && + GetCapaVendorMetaDataLc3QPref(dst)) { + LOG(INFO) << __func__ << ": Matching Codec LC3Q Found " + << ", for direction: " << loghex(stream_type.direction); + uint8_t lc3qVer = GetCapaVendorMetaDataLc3QVer(dst); + UpdateVendorMetaDataLc3QPref(src, true); + UpdateVendorMetaDataLc3QVer(src, lc3qVer); + } else { + LOG(INFO) << __func__ << ": LC3Q not prefered, going with LC3 " + << "for direction: " << loghex(stream_type.direction); + } + codec_found = true; + break; + } + } + if(codec_found) break; + } + if(codec_found) return index; + else return 0xFF; +} + +// fine tuning the QOS params (RTN, MTL, PD) based on +// remote device preferences +bool StreamTracker::ChooseBestQos(QosConfig *src_config, + ascs::AseCodecConfigParams *rem_qos_prefs, + QosConfig *dst_config, + int stream_state, + uint8_t stream_direction) { + uint8_t final_rtn = 0xFF; + uint16_t final_mtl = 0xFFFF; + uint32_t req_pd = (src_config->ascs_configs[0].presentation_delay[0] | + src_config->ascs_configs[0].presentation_delay[1] << 8 | + src_config->ascs_configs[0].presentation_delay[2] << 16); + + uint32_t rem_pd_min = (rem_qos_prefs->pd_min[0] | + rem_qos_prefs->pd_min[1] << 8 | + rem_qos_prefs->pd_min[2] << 16); + + uint32_t rem_pd_max = (rem_qos_prefs->pd_max[0] | + rem_qos_prefs->pd_max[1] << 8 | + rem_qos_prefs->pd_max[2] << 16); + + uint32_t rem_pref_pd_min = (rem_qos_prefs->pref_pd_min[0] | + rem_qos_prefs->pref_pd_min[1] << 8 | + rem_qos_prefs->pref_pd_min[2] << 16); + + uint32_t rem_pref_pd_max = (rem_qos_prefs->pref_pd_max[0] | + rem_qos_prefs->pref_pd_max[1] << 8 | + rem_qos_prefs->pref_pd_max[2] << 16); + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector streams = audio_strms->FindByCigId( + src_config->ascs_configs[0].cig_id, + stream_state); + + // check if the RTN and MTL is with in the limits + if(stream_direction == ASE_DIRECTION_SINK) { + if(src_config->cis_configs[0].rtn_m_to_s > rem_qos_prefs->pref_rtn) { + final_rtn = rem_qos_prefs->pref_rtn; + } + if(src_config->cig_config.max_tport_latency_m_to_s > rem_qos_prefs->mtl) { + final_mtl = rem_qos_prefs->mtl; + } + } else if(stream_direction == ASE_DIRECTION_SRC) { + if(src_config->cis_configs[0].rtn_s_to_m > rem_qos_prefs->pref_rtn) { + final_rtn = rem_qos_prefs->pref_rtn; + } + if(src_config->cig_config.max_tport_latency_s_to_m > rem_qos_prefs->mtl) { + final_mtl = rem_qos_prefs->mtl; + } + } + + LOG(INFO) << __func__ << " req_pd: " << loghex(req_pd) + << " rem_pd_min: " << loghex(rem_pd_min) + << " rem_pd_max: " << loghex(rem_pd_max) + << " rem_pref_pd_min: " << loghex(rem_pref_pd_min) + << " rem_pref_pd_max: " << loghex(rem_pref_pd_max); + + // check if PD is within the limits + if(rem_pref_pd_min && rem_pref_pd_max) { + if(req_pd < rem_pref_pd_min) { + memcpy(&dst_config->ascs_configs[0].presentation_delay, + &rem_qos_prefs->pref_pd_min, + sizeof(dst_config->ascs_configs[0].presentation_delay)); + } else if(req_pd > rem_pref_pd_max) { + memcpy(&dst_config->ascs_configs[0].presentation_delay, + &rem_qos_prefs->pref_pd_max, + sizeof(dst_config->ascs_configs[0].presentation_delay)); + } + } else { + if(req_pd != rem_pd_min) { + memcpy(&dst_config->ascs_configs[0].presentation_delay, + &rem_qos_prefs->pd_min, + sizeof(dst_config->ascs_configs[0].presentation_delay)); + } + } + + // check if anything to be updated for all streams + if(final_rtn == 0xFF && final_mtl == 0XFFFF) { + LOG(WARNING) << __func__ << " No fine tuning for QOS params"; + return true; + } else if(final_rtn != 0xFF) { + LOG(WARNING) << __func__ << " Updated RTN to " << loghex(final_rtn); + } else if(final_mtl != 0XFFFF) { + LOG(WARNING) << __func__ << " Updated MTL to " << loghex(final_mtl); + } + + for (auto i = streams.begin(); i != streams.end();i++) { + UcastAudioStream *stream = (*i); + if(stream_direction == ASE_DIRECTION_SINK) { + if(final_mtl != 0xFFFF) { + stream->qos_config.cig_config.max_tport_latency_m_to_s = final_mtl; + } + if(final_rtn != 0xFF) { + for (auto it = stream->qos_config.cis_configs.begin(); + it != stream->qos_config.cis_configs.end(); it++) { + (*it).rtn_m_to_s = final_rtn; + } + } + } else if(stream_direction == ASE_DIRECTION_SRC) { + if(final_mtl != 0xFFFF) { + stream->qos_config.cig_config.max_tport_latency_s_to_m = final_mtl; + } + if(final_rtn != 0xFF) { + for (auto it = stream->qos_config.cis_configs.begin(); + it != stream->qos_config.cis_configs.end(); it++) { + (*it).rtn_s_to_m = final_rtn; + } + } + } + } + return true; +} + +bool StreamTracker::HandlePacsConnectionEvent(void *p_data) { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + if(pacs_state->state == ConnectionState::CONNECTED) { + LOG(INFO) << __func__ << " PACS server connected"; + } else if(pacs_state->state == ConnectionState::DISCONNECTED) { + HandleInternalDisconnect(false); + } + return true; +} + +bool StreamTracker::HandlePacsAudioContextEvent( + PacsAvailableContexts *pacs_contexts) { + std::vector *update_streams = GetMetaUpdateStreams(); + uint8_t contexts_supported = 0; + + // check if supported audio contexts has required contexts + for(auto it = update_streams->begin(); it != update_streams->end(); it++) { + if(it->update_type == StreamUpdateType::STREAMING_CONTEXT) { + if(it->stream_type.direction == ASE_DIRECTION_SINK) { + if(it->update_value & pacs_contexts->available_contexts) { + contexts_supported++; + } + } else if(it->stream_type.direction == ASE_DIRECTION_SRC) { + if((static_cast(it->update_value) << 16) & + pacs_contexts->available_contexts) { + contexts_supported++; + } + } + } + } + + if(contexts_supported != update_streams->size()) { + LOG(ERROR) << __func__ << ": No Matching available Contexts found"; + return false; + } else { + return true; + } +} + +bool StreamTracker::HandleCisEventsInStreaming(void* p_data) { + CisStreamState *data = (CisStreamState *) p_data; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CisState::READY) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + } + TransitionTo(StreamTracker::kStateStopping); + } + return true; +} + +bool StreamTracker::HandleAscsConnectionEvent(void *p_data) { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + if(ascs_state->state == GattState::CONNECTED) { + LOG(INFO) << __func__ << "ASCS server connected"; + } else if(ascs_state->state == GattState::DISCONNECTED) { + // make all streams ASE state ot idle so that further processing + // can happen + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector *strms_list = audio_strms->GetAllStreams(); + + for (auto it = strms_list->begin(); it != strms_list->end(); it++) { + (*it)->ase_state = ascs::ASE_STATE_IDLE; + (*it)->ase_pending_cmd = AscsPendingCmd::NONE; + (*it)->overall_state = StreamTracker::kStateIdle; + } + HandleInternalDisconnect(false); + } + return true; +} + +bool StreamTracker::ValidateAseUpdate(void* p_data, + IntStrmTrackers *int_strm_trackers, + int exp_strm_state) { + AscsState *ascs = ((AscsState *) p_data); + + uint8_t ase_id = ascs->ase_params.ase_id; + + // check if current stream tracker is interested in this ASE update + if(int_strm_trackers->FindByAseId(ase_id) + == nullptr) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + return false; + } + + // find out the stream for the given ase id + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + LOG(INFO) << __func__ << ": Streams Size = " << audio_strms->size() + << ": ASE Id = " << loghex(ase_id); + + if (stream == nullptr || stream->overall_state != exp_strm_state) { + LOG(WARNING) << __func__ << "No Audio Stream found"; + return false; + } + + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + return true; +} + +bool StreamTracker::HandleRemoteDisconnect(uint32_t event, + void* p_data, int cur_state) { + UpdateControlType(StreamControlType::Disconnect); + std::vector streams; + + switch(cur_state) { + case StreamTracker::kStateConnecting: { + std::vector *conn_streams = GetConnStreams(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + case StreamTracker::kStateReconfiguring: { + std::vector *reconf_streams = GetReconfStreams(); + + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + } + + // update the state to disconnecting + TransitionTo(StreamTracker::kStateDisconnecting); + ProcessEvent(event, p_data); + return true; +} + +bool StreamTracker::StreamCanbeDisconnected(StreamContext *cur_context, + uint8_t ase_id) { + bool can_be_disconnected = false; + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + StreamAttachedState state = (StreamAttachedState) + (static_cast (StreamAttachedState::PHYSICAL) | + static_cast (StreamAttachedState::IDLE_TO_PHY) | + static_cast (StreamAttachedState::VIR_TO_PHY)); + + std::vector attached_contexts = + contexts->FindByAseAttachedState(ase_id, state); + + LOG(INFO) << __func__ <<": attached_contexts: size : " + << attached_contexts.size(); + if(cur_context->attached_state == StreamAttachedState::PHYSICAL || + cur_context->attached_state == StreamAttachedState::IDLE_TO_PHY || + cur_context->attached_state == StreamAttachedState::VIR_TO_PHY ) { + can_be_disconnected = true; + } + return can_be_disconnected; +} + +bool StreamTracker::HandleInternalDisconnect(bool release) { + + UpdateControlType(StreamControlType::Disconnect); + + std::vector streams; + + int cur_state = StateId(); + LOG(WARNING) << __func__ <<": cur_state: " << cur_state + <<", release: " << release; + switch(cur_state) { + case StreamTracker::kStateConnecting: { + std::vector *conn_streams = GetConnStreams(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + case StreamTracker::kStateReconfiguring: { + std::vector *reconf_streams = GetReconfStreams(); + + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + } + + if (release) { + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector ase_ops; + std::vector *disc_streams = GetStreams(); + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + bool can_be_disconnected = StreamCanbeDisconnected(context, id->ase_id); + if (can_be_disconnected && + stream && stream->overall_state == cur_state && + stream->ase_state != ascs::ASE_STATE_IDLE && + stream->ase_state != ascs::ASE_STATE_RELEASING && + stream->ase_pending_cmd != AscsPendingCmd::RELEASE_ISSUED) { + LOG(WARNING) << __func__ + <<": ASE State : " << loghex(stream->ase_state); + AseReleaseOp release_op = { + .ase_id = stream->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + // change the overall state to Disconnecting + stream->overall_state = StreamTracker::kStateDisconnecting; + } + } + } + + // send consolidated release command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Release op"; + ascs_client->Release(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + } + // update the state to disconnecting + TransitionTo(StreamTracker::kStateDisconnecting); + return true; +} + +bool StreamTracker::HandleRemoteStop(uint32_t event, + void* p_data, int cur_state) { + AscsState *ascs = ((AscsState *) p_data); + uint8_t ase_id = ascs->ase_params.ase_id; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(!stream) return false; + + if(stream->direction & cis::DIR_TO_AIR) { + LOG(ERROR) << __func__ << ": Invalid State transition to Disabling" + << ": ASE Id = " << loghex(ase_id); + return false; + } + + UpdateControlType(StreamControlType::Stop); + + if(cur_state != StreamTracker::kStateStarting || + cur_state != StreamTracker::kStateStreaming) { + return false; + } + // update the state to stopping + TransitionTo(StreamTracker::kStateStopping); + ProcessEvent(event, p_data); + return true; +} + +bool StreamTracker::HandleAbruptStop(uint32_t event, void* p_data) { + AscsState *ascs = ((AscsState *) p_data); + uint8_t ase_id = ascs->ase_params.ase_id; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(!stream) return false; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + + UpdateControlType(StreamControlType::Stop); + + // update the state to stopping + TransitionTo(StreamTracker::kStateStopping); + return true; +} + +bool StreamTracker::HandleRemoteReconfig(uint32_t event, + void* p_data, int cur_state) { + UpdateControlType(StreamControlType::Reconfig); + std::vector streams; + + if(cur_state != StreamTracker::kStateConnected) { + return false; + } + // update the state to Reconfiguring + TransitionTo(StreamTracker::kStateReconfiguring); + ProcessEvent(event, p_data); + return true; +} + +void StreamTracker::HandleAseOpFailedEvent(void *p_data) { + AscsOpFailed *ascs_op = ((AscsOpFailed *) p_data); + std::vector *ase_list = &ascs_op->ase_list; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + if(ascs_op->ase_op_id == ascs::AseOpId::CODEC_CONFIG) { + // treat it like internal failure + for (auto i = ase_list->begin(); i != ase_list->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((i)->ase_id); + if(stream) { + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + } + } + HandleInternalDisconnect(false); + } else { + HandleInternalDisconnect(true); + } +} + +void StreamTracker::HandleAseStateEvent(void *p_data, + StreamControlType control_type, + IntStrmTrackers *int_strm_trackers) { + // check the state and if the state is codec configured for all ASEs + // then proceed with group creation + AscsState *ascs = reinterpret_cast (p_data); + + uint8_t ase_id = ascs->ase_params.ase_id; + + // check if current stream tracker is interested in this ASE update + if(int_strm_trackers->FindByAseId(ase_id) == nullptr) { + LOG(INFO) << __func__ << ": Not intended for this tracker"; + return; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(stream == nullptr) { + return; + } else { + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + } + + if(ascs->ase_params.ase_state == ascs::ASE_STATE_CODEC_CONFIGURED) { + stream->pref_qos_params = ascs->ase_params.codec_config_params; + // find out the stream for the given ase id + LOG(INFO) << __func__ << ": Total Num Streams = " << audio_strms->size() + << ": ASE Id = " << loghex(ase_id); + + // decide on best QOS params by comparing the upper layer prefs + // and remote dev's preferences + int state = StreamTracker::kStateIdle; + + if(control_type == StreamControlType::Connect) { + state = StreamTracker::kStateConnecting; + } else if(control_type == StreamControlType::Reconfig) { + state = StreamTracker::kStateReconfiguring; + } + + // check for all trackers codec is configured or not + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + uint8_t num_codec_configured = 0; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_pending_cmd == AscsPendingCmd::NONE && + (stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED || + (control_type == StreamControlType::Reconfig && + stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED))) { + num_codec_configured++; + } + } + + if(int_strm_trackers->size() != num_codec_configured) { + LOG(WARNING) << __func__ << " Codec Not Configured For All Streams"; + return; + } + + // check for all streams together so that final group params + // will be decided + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + ChooseBestQos(&stream->req_qos_config, &stream->pref_qos_params, + &stream->qos_config, state, stream->direction); + } + } + CheckAndSendQosConfig(int_strm_trackers); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + // TODO update the state as connected using callbacks + // make the state transition to connected + + // check for all trackers QOS is configured or not + // if so update it as streams are connected + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + uint8_t num_qos_configured = 0; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED && + stream->ase_pending_cmd == AscsPendingCmd::NONE) { + num_qos_configured++; + } + } + + if(int_strm_trackers->size() != num_qos_configured) { + LOG(WARNING) << __func__ << " QOS Not Configured For All Streams"; + return; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + StreamContext *context = contexts->FindOrAddByType( + (*i)->strm_type); + if(context->attached_state == StreamAttachedState::IDLE_TO_PHY || + context->attached_state == StreamAttachedState::VIR_TO_PHY) { + context->attached_state = StreamAttachedState::PHYSICAL; + LOG(INFO) << __func__ << " Attached state made physical"; + } + stream->overall_state = kStateConnected; + } + + // update the state to connected + TransitionTo(StreamTracker::kStateConnected); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } +} + +bool StreamTracker::HandleStreamUpdate (int cur_state) { + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + std::vector ase_meta_ops; + std::vector *update_streams = GetMetaUpdateStreams(); + + for (auto it = update_streams->begin(); + it != update_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + std::vector meta_data; + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if(stream && stream->ase_state != ascs::ASE_STATE_ENABLING && + stream->ase_state != ascs::ASE_STATE_STREAMING) { + continue; + } + if(it->update_type == StreamUpdateType::STREAMING_CONTEXT) { + uint8_t len = LTV_LEN_STRM_AUDIO_CONTEXTS; + uint8_t type = LTV_TYPE_STRM_AUDIO_CONTEXTS; + uint16_t value = it->update_value; + if(stream) stream->audio_context = value; + meta_data.insert(meta_data.end(), &len, &len + 1); + meta_data.insert(meta_data.end(), &type, &type + 1); + meta_data.insert(meta_data.end(), ((uint8_t *)&value), + ((uint8_t *)&value) + sizeof(uint16_t)); + } + + AseUpdateMetadataOp meta_op = { + .ase_id = id->ase_id, + .meta_data_len = + static_cast (meta_data.size()), + .meta_data = meta_data // media or voice + }; + ase_meta_ops.push_back(meta_op); + } + } + + // send consolidated update meta command to ASCS client + if(ase_meta_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Update MetaData op"; + ascs_client->UpdateStream(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), + ase_meta_ops); + } else { + return false; + } + + if(cur_state == StreamTracker::kStateUpdating) { + for (auto it = update_streams->begin(); + it != update_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr) { + // change the connection state to disable issued + stream->ase_pending_cmd = AscsPendingCmd::UPDATE_METADATA_ISSUED; + // change the overall state to Updating + stream->overall_state = StreamTracker::kStateUpdating; + } + } + } + } + return true; +} + +bool StreamTracker::HandleStop(void* p_data, int cur_state) { + if(p_data != nullptr) { + BapStop *evt_data = (BapStop *) p_data; + UpdateStreams(&evt_data->streams); + } + UpdateControlType(StreamControlType::Stop); + + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + std::vector ase_ops; + std::vector *stop_streams = GetStreams(); + + for (auto it = stop_streams->begin(); + it != stop_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + AseDisableOp disable_op = { + .ase_id = id->ase_id + }; + ase_ops.push_back(disable_op); + } + } + + // send consolidated disable command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Disable op"; + ascs_client->Disable(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + + for (auto it = stop_streams->begin(); + it != stop_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr && stream->overall_state == cur_state) { + // change the connection state to disable issued + stream->ase_pending_cmd = AscsPendingCmd::DISABLE_ISSUED; + // change the overall state to stopping + stream->overall_state = StreamTracker::kStateStopping; + } + } + } + TransitionTo(StreamTracker::kStateStopping); + return true; +} + +bool StreamTracker::HandleDisconnect(void* p_data, int cur_state) { + // expect the disconnection for same set of streams connection + // has initiated ex: if connect is issued for media (tx), voice(tx & rx) + // then disconnect is expected for media (tx), voice(tx & rx). + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + BapDisconnect *evt_data = (BapDisconnect *) p_data; + + UpdateControlType(StreamControlType::Disconnect); + + UpdateStreams(&evt_data->streams); + + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + std::vector ase_ops; + std::vector *disc_streams = GetStreams(); + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + bool can_be_disconnected = StreamCanbeDisconnected(context, id->ase_id); + if(can_be_disconnected && + stream && stream->overall_state != StreamTracker::kStateIdle && + stream->overall_state != StreamTracker::kStateDisconnecting && + stream->ase_pending_cmd != AscsPendingCmd::RELEASE_ISSUED) { + AseReleaseOp release_op = { + .ase_id = id->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + // change the overall state to starting + stream->overall_state = StreamTracker::kStateDisconnecting; + } + } + } + + // send consolidated release command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Release op"; + ascs_client->Release(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + + TransitionTo(StreamTracker::kStateDisconnecting); + return true; +} + +void StreamTracker::CheckAndSendQosConfig(IntStrmTrackers *int_strm_trackers) { + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + // check for all trackers CIG is created or not + // if so proceed with QOS config operaiton + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + + std::vector ase_ops; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + QosConfig *qos_config = &stream->qos_config; + if(!stream || stream->ase_pending_cmd != AscsPendingCmd::NONE) { + continue; + } + if(stream->direction & cis::DIR_TO_AIR) { + + AseQosConfigOp qos_config_op = { + .ase_id = (*i)->ase_id, + .cig_id = stream->cig_id, + .cis_id = stream->cis_id, + .sdu_interval = { qos_config->cig_config.sdu_interval_m_to_s[0], + qos_config->cig_config.sdu_interval_m_to_s[1], + qos_config->cig_config.sdu_interval_m_to_s[2] }, + .framing = qos_config->cig_config.framing, + .phy = LE_2M_PHY, + .max_sdu_size = qos_config->cis_configs[(*i)->cis_id].max_sdu_m_to_s, + .retrans_number = qos_config->cis_configs[(*i)->cis_id].rtn_m_to_s, + .trans_latency = qos_config->cig_config.max_tport_latency_m_to_s, + .present_delay = {qos_config->ascs_configs[0].presentation_delay[0], + qos_config->ascs_configs[0].presentation_delay[1], + qos_config->ascs_configs[0].presentation_delay[2]} + }; + ase_ops.push_back(qos_config_op); + + } else if(stream->direction & cis::DIR_FROM_AIR) { + AseQosConfigOp qos_config_op = { + .ase_id = (*i)->ase_id, + .cig_id = stream->cig_id, + .cis_id = stream->cis_id, + .sdu_interval = { qos_config->cig_config.sdu_interval_s_to_m[0], + qos_config->cig_config.sdu_interval_s_to_m[1], + qos_config->cig_config.sdu_interval_s_to_m[2] }, + .framing = qos_config->cig_config.framing, + .phy = LE_2M_PHY, + .max_sdu_size = qos_config->cis_configs[(*i)->cis_id].max_sdu_s_to_m, + .retrans_number = qos_config->cis_configs[(*i)->cis_id].rtn_s_to_m, + .trans_latency = qos_config->cig_config.max_tport_latency_s_to_m, + .present_delay = {qos_config->ascs_configs[0].presentation_delay[0], + qos_config->ascs_configs[0].presentation_delay[1], + qos_config->ascs_configs[0].presentation_delay[2]} + }; + ase_ops.push_back(qos_config_op); + } + } + + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS QosConfig op"; + ascs_client->QosConfig(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && stream->ase_pending_cmd == AscsPendingCmd::NONE) + stream->ase_pending_cmd = AscsPendingCmd::QOS_CONFIG_ISSUED; + } + } +} + + +void StreamTracker::CheckAndSendEnable(IntStrmTrackers *int_strm_trackers) { + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + std::vector *start_streams = GetStreams(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector ase_ops; + // check for all trackers CIG is created or not + // if so proceed with QOS config operaiton + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + + uint8_t num_cig_created = 0; + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && stream->cig_state == CigState::CREATED) { + num_cig_created++; + } + } + + if(int_strm_trackers->size() != num_cig_created) { + LOG(WARNING) << __func__ << " All CIGs are not created"; + return; + } + + for(auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + std::vector meta_data; + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + uint8_t len = LTV_LEN_STRM_AUDIO_CONTEXTS; + uint8_t type = LTV_TYPE_STRM_AUDIO_CONTEXTS; + uint16_t value = (*it).audio_context; + if(stream) stream->audio_context = value; + meta_data.insert(meta_data.end(), &len, &len + 1); + meta_data.insert(meta_data.end(), &type, &type + 1); + meta_data.insert(meta_data.end(), ((uint8_t *)&value), + ((uint8_t *)&value) + sizeof(uint16_t)); + + AseEnableOp enable_op = { + .ase_id = id->ase_id, + .meta_data_len = + static_cast (meta_data.size()), + .meta_data = meta_data // media or voice + }; + ase_ops.push_back(enable_op); + } + } + + // send consolidated enable command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Enable op"; + ascs_client->Enable(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr && stream->overall_state == + StreamTracker::kStateConnected) { + // change the connection state to enable issued + stream->ase_pending_cmd = AscsPendingCmd::ENABLE_ISSUED; + // change the overall state to starting + stream->overall_state = StreamTracker::kStateStarting; + } + } + } + } +} + +void StreamTracker::HandleCigStateEvent(uint32_t event, void *p_data, + IntStrmTrackers *int_strm_trackers) { + // check if the associated CIG state is created + // if so go for Enable Operation + CisGroupState *data = ((CisGroupState *) p_data); + + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers->FindByCigId(data->cig_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << " Not intended for this tracker"; + return; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CigState::CREATED) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cig_state = data->state; + stream->cis_state = CisState::READY; + } + } + CheckAndSendEnable(int_strm_trackers); + } else if(data->state == CigState::IDLE) { + // CIG state is idle means there is some failure + LOG(ERROR) << __func__ << ": CIG Creation Failed"; + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->cis_pending_cmd = CisPendingCmd::NONE;; + } + } + HandleInternalDisconnect(false); + return; + } +} + +bool StreamTracker::PrepareCodecConfigPayload( + std::vector *ase_ops, + UcastAudioStream *stream) { + std::vector codec_params; + uint8_t tgt_latency = TGT_HIGH_RELIABLE; + // check for sampling freq + for (auto it : freq_to_ltv_map) { + if(stream->codec_config.sample_rate == it.first) { + uint8_t len = LTV_LEN_SAMP_FREQ; + uint8_t type = LTV_TYPE_SAMP_FREQ; + uint8_t rate = it.second; + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), &rate, &rate + 1); + break; + } + } + + // check for frame duration and fetch 5th byte + uint8_t frame_duration = GetFrameDuration(&stream->codec_config); + uint8_t len = LTV_LEN_FRAME_DUR; + uint8_t type = LTV_TYPE_FRAME_DUR; + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), &frame_duration, + &frame_duration + 1); + + // audio chnl allcation + if(stream->audio_location) { + uint8_t len = LTV_LEN_CHNL_ALLOC; + uint8_t type = LTV_TYPE_CHNL_ALLOC; + uint32_t value = stream->audio_location; + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), ((uint8_t *)&value), + ((uint8_t *)&value) + sizeof(uint32_t)); + } + + // octets per frame + len = LTV_LEN_OCTS_PER_FRAME; + type = LTV_TYPE_OCTS_PER_FRAME; + uint16_t octs_per_frame = GetOctsPerFrame(&stream->codec_config); + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), ((uint8_t *)&octs_per_frame), + ((uint8_t *)&octs_per_frame) + sizeof(uint16_t)); + + // blocks per SDU + len = LTV_LEN_FRAMES_PER_SDU; + type = LTV_TYPE_FRAMES_PER_SDU; + uint8_t blocks_per_sdu = GetLc3BlocksPerSdu(&stream->codec_config); + // initialize it to 1 if it doesn't exists + if(!blocks_per_sdu) { + blocks_per_sdu = 1; + } + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), &blocks_per_sdu, + &blocks_per_sdu + 1); + + if(stream->audio_context == CONTENT_TYPE_MEDIA) { + tgt_latency = TGT_HIGH_RELIABLE; + } else if(stream->audio_context == CONTENT_TYPE_CONVERSATIONAL) { + tgt_latency = TGT_BAL_LATENCY; + } else if(stream->audio_context == CONTENT_TYPE_GAME) { + tgt_latency = TGT_LOW_LATENCY; + } + + AseCodecConfigOp codec_config_op = { + .ase_id = stream->ase_id, + .tgt_latency = tgt_latency, + .tgt_phy = LE_2M_PHY, + .codec_id = {LC3_CODEC_ID, 0, 0, 0, 0}, + .codec_params_len = + static_cast (codec_params.size()), + .codec_params = codec_params + }; + ase_ops->push_back(codec_config_op); + return true; +} + +alarm_t* StreamTracker::SetTimer(const char* alarmname, + BapTimeout* timeout, TimeoutReason reason, uint64_t ms) { + alarm_t* timer = nullptr; + + timeout->bd_addr = strm_mgr_->GetAddress(); + timeout->tracker = this; + timeout->reason = reason; + timeout->transition_state = StateId(); + + BapAlarm* bap_alarm = strm_mgr_->GetBapAlarm(); + if (bap_alarm != nullptr) { + timer = bap_alarm->Create(alarmname); + if (timer == nullptr) { + LOG(ERROR) << __func__ << ": Not able to create alarm"; + return nullptr; + } + LOG(INFO) << __func__ << ": starting " << alarmname; + bap_alarm->Start(timer, ms, timeout); + } + return timer; +} + +void StreamTracker::ClearTimer(alarm_t* timer, const char* alarmname) { + BapAlarm* bap_alarm = strm_mgr_->GetBapAlarm(); + + if (bap_alarm != nullptr && bap_alarm->IsScheduled(timer)) { + LOG(INFO) << __func__ << ": clear " << alarmname; + bap_alarm->Stop(timer); + } +} + +void StreamTracker::OnTimeout(void* data) { + BapTimeout* timeout = (BapTimeout *)data; + if (timeout == nullptr) { + LOG(INFO) << __func__ << ": timeout data null, return "; + return; + } + + bool isReleaseNeeded = false; + int stream_tracker_id = timeout->transition_state; + LOG(INFO) << __func__ << ": stream_tracker_ID: " << stream_tracker_id + << ", timeout reason: " << static_cast(timeout->reason); + + if (timeout->reason == TimeoutReason::STATE_TRANSITION) { + if (stream_tracker_id == StreamTracker::kStateConnecting) { + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector *conn_streams = GetConnStreams(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + LOG(WARNING) << __func__ << ": audio_strms: " << audio_strms->size() + << ", conn_streams: " << conn_streams->size(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + LOG(INFO) << __func__ << ": connection_state: " + << static_cast(context->connection_state); + + if(context->connection_state == IntConnectState::ASCS_DISCOVERED) { + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + + LOG(INFO) << __func__ << ": ase_state: " << stream->ase_state; + if (stream->ase_state != ascs::ASE_STATE_IDLE && + stream->ase_state != ascs::ASE_STATE_RELEASING) { + LOG(WARNING) << __func__ + << ": ascs state is neither idle nor releasing"; + isReleaseNeeded = true; + break; + } + } + } + } + LOG(INFO) << __func__ << ": isReleaseNeeded: " << isReleaseNeeded; + HandleInternalDisconnect(isReleaseNeeded); + } else if (stream_tracker_id != StreamTracker::kStateDisconnecting) { + //All other transient states + HandleInternalDisconnect(true); + } + } + LOG(INFO) << __func__ << ": Exit"; +} + +void StreamTracker::StateIdle::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Disconnect && + control_type != StreamControlType::Connect) { + return; + } + + if(control_type == StreamControlType::Disconnect) { + std::vector *disc_streams = tracker_.GetStreams(); + LOG(WARNING) << __func__ << ": Disc Streams Size: " + << disc_streams->size(); + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::DISCONNECTED; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::DISCONNECTED; + context->attached_state = StreamAttachedState::IDLE; + LOG(INFO) << __func__ << " Attached state made idle"; + context->stream_ids.clear(); + } + } else if(control_type == StreamControlType::Connect) { + std::vector *conn_streams = tracker_.GetConnStreams(); + uint32_t prev_state = tracker_.PreviousStateId(); + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::DISCONNECTED; + context->attached_state = StreamAttachedState::IDLE; + LOG(INFO) << __func__ << " Attached state made idle"; + context->stream_ids.clear(); + if(prev_state == StreamTracker::kStateConnecting) { + state.stream_type = it->stream_type; + state.stream_state = StreamState::DISCONNECTED; + strms.push_back(state); + } + } + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); +} + +void StreamTracker::StateIdle::OnExit() { + +} + +bool StreamTracker::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + + switch (event) { + case BAP_CONNECT_REQ_EVT: { + BapConnect *evt_data = (BapConnect *) p_data; + // check if the PACS client is connected to remote device + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + ConnectionState pacs_state = strm_mgr_->GetPacsState(); + if(pacs_state == ConnectionState::DISCONNECTED || + pacs_state == ConnectionState::DISCONNECTING || + pacs_state == ConnectionState::CONNECTING) { + // move the state to connecting and initiate pacs connection + pacs_client->Connect(pacs_client_id, strm_mgr_->GetAddress(), + evt_data->is_direct); + if(gatt_pending_data->pacs_pending_cmd == GattPendingCmd::NONE) { + gatt_pending_data->pacs_pending_cmd = + GattPendingCmd::GATT_CONN_PENDING; + } + tracker_.TransitionTo(StreamTracker::kStateConnecting); + } else if(pacs_state == ConnectionState::CONNECTED) { + // pacs is already connected so initiate + // pacs service discovry now and move the state to connecting + pacs_client->StartDiscovery(pacs_client_id, strm_mgr_->GetAddress()); + tracker_.TransitionTo(StreamTracker::kStateConnecting); + } + } break; + default: + LOG(WARNING) << __func__ << "Unhandled Event" << loghex(event); + break; + } + return true; +} + +void StreamTracker::StateConnecting::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *conn_streams = tracker_.GetConnStreams(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Connect) return; + + ConnectionState pacs_state = strm_mgr_->GetPacsState(); + + LOG(INFO) << __func__ << ": Conn Streams Size: " << conn_streams->size(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::CONNECTING; + if(pacs_state == ConnectionState::DISCONNECTED || + pacs_state == ConnectionState::CONNECTING) { + context->connection_state = IntConnectState::PACS_CONNECTING; + } else if(pacs_state == ConnectionState::CONNECTED) { + context->connection_state = IntConnectState::PACS_DISCOVERING; + } + state.stream_type = it->stream_type; + state.stream_state = StreamState::CONNECTING; + strms.push_back(state); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateConnectingTimer", + &timeout, reason, ((conn_streams->size()) * + (static_cast(TimeoutVal::ConnectingTimeout)))); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateConnecting: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateConnecting::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateConnectingTimer"); +} + +void StreamTracker::StateConnecting::DeriveDeviceType( + PacsDiscovery *pacs_discovery) { + // derive the device type based on sink pac records + std::vector *pac_records = &pacs_discovery->sink_pac_records; + uint8_t max_chnl_count = 0; + + // chnl count Audio location Type of device No of ASEs + // 1 Left or Right Earbud 1 ASE per Earbud + // 1 Left and Right Stereo Headset ( 2 CIS) 2 ASEs + // 2 Left and Right Stereo Headset ( 1 CIS) 1 ASE + // 2 Left or Right Earbud 1 ASE per Earbud + + for (auto j = pac_records->begin(); j != pac_records->end();j++) { + CodecConfig *dst = &(*j); + if(static_cast (dst->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_STEREO)) { + max_chnl_count = 2; + } else if(!max_chnl_count && + static_cast (dst->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_MONO)) { + max_chnl_count = 1; + } + } + + if(pacs_discovery->sink_locations & ucast::AUDIO_LOC_LEFT && + pacs_discovery->sink_locations & ucast::AUDIO_LOC_RIGHT) { + if(max_chnl_count == 2) { + strm_mgr_->UpdateDevType(DeviceType::HEADSET_STEREO); + } else if (max_chnl_count == 1) { + strm_mgr_->UpdateDevType(DeviceType::HEADSET_SPLIT_STEREO); + } + } else if(pacs_discovery->sink_locations & ucast::AUDIO_LOC_LEFT || + pacs_discovery->sink_locations & ucast::AUDIO_LOC_RIGHT) { + strm_mgr_->UpdateDevType(DeviceType::EARBUD); + } +} + + +bool StreamTracker::StateConnecting::AttachStreamsToContext( + std::vector *all_trackers, + std::vector *streams, + uint8_t cis_count, + std::vector *ase_ops) { + PacsDiscovery *pacs_discovery_ = tracker_.GetPacsDiscovery(); + if (!pacs_discovery_) { + return false; + } + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + for(uint8_t i = 0; i < all_trackers->size()/cis_count ; i++) { + for(uint8_t j = 0; j < cis_count ; j++) { + IntStrmTracker *tracker = all_trackers->at(i*cis_count + j); + UcastAudioStream *stream = streams->at((i*cis_count + j)% streams->size()); + StreamContext *context = contexts->FindOrAddByType( + tracker->strm_type); + if(stream->overall_state == StreamTracker::kStateIdle) { + stream->audio_context = tracker->strm_type.audio_context; + stream->control_type = StreamControlType::Connect; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->codec_config = tracker->codec_config; + stream->req_qos_config = tracker->qos_config; + stream->qos_config = tracker->qos_config; + stream->cig_id = tracker->cig_id; + stream->cis_id = tracker->cis_id; + + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->overall_state = StreamTracker::kStateConnecting; + + if (stream->direction == ASE_DIRECTION_SINK) { + if(cis_count > 1) { + stream->audio_location = + pacs_discovery_->sink_locations & locations.at(j); + } else { + if(pacs_discovery_->sink_locations & ucast::AUDIO_LOC_LEFT && + pacs_discovery_->sink_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = 0; + } else if(pacs_discovery_->sink_locations & ucast::AUDIO_LOC_LEFT || + pacs_discovery_->sink_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = pacs_discovery_->sink_locations; + } + } + } else if (stream->direction == ASE_DIRECTION_SRC) { + if(cis_count > 1) { + stream->audio_location = + pacs_discovery_->src_locations & locations.at(j); + } else { + if(pacs_discovery_->src_locations & ucast::AUDIO_LOC_LEFT && + pacs_discovery_->src_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = 0; + } else if(pacs_discovery_->src_locations & ucast::AUDIO_LOC_LEFT || + pacs_discovery_->src_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = pacs_discovery_->src_locations; + } + } + } + tracker_.PrepareCodecConfigPayload(ase_ops, stream); + tracker->attached_state = context->attached_state = + StreamAttachedState::IDLE_TO_PHY; + LOG(INFO) << __func__ + << ": Physically attached"; + } else { + LOG(INFO) << __func__ + << ": Virtually attached"; + tracker->attached_state = context->attached_state = + StreamAttachedState::VIRTUAL; + } + tracker->ase_id = stream->ase_id; + + StreamIdType id = { + .ase_id = stream->ase_id, + .ase_direction = stream->direction, + .virtual_attach = false, + .cig_id = tracker->cig_id, + .cis_id = tracker->cis_id + }; + context->stream_ids.push_back(id); + } + } + return true; +} + +bool StreamTracker::StateConnecting::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + std::vector *conn_streams = tracker_.GetConnStreams(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + + uint8_t num_conn_streams = 0; + if(conn_streams) { + num_conn_streams = conn_streams->size(); + } + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + + // expect the disconnection for same set of streams connection + // has initiated ex: if connect is issued for media (tx), voice(tx & rx) + // then disconnect is expected for media (tx), voice(tx & rx). + + // based on connection state, issue the relevant commands and move + // the state to disconnecting + // issue the release opertion if for any stream ASE operation is + // initiated + + // upate the control type and streams also + BapDisconnect *evt_data = (BapDisconnect *) p_data; + + tracker_.UpdateControlType(StreamControlType::Disconnect); + + tracker_.UpdateStreams(&evt_data->streams); + + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + std::vector ase_ops; + std::vector *disc_streams = tracker_.GetStreams(); + + LOG(WARNING) << __func__ << ": disc_streams: " << disc_streams->size(); + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(context->connection_state == IntConnectState::ASCS_DISCOVERED) { + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + bool can_be_disconnected = + tracker_.StreamCanbeDisconnected(context, id->ase_id); + if(can_be_disconnected && + stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED && + stream->ase_pending_cmd != AscsPendingCmd::RELEASE_ISSUED) { + AseReleaseOp release_op = { + .ase_id = stream->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + // change the overall state to starting + stream->overall_state = StreamTracker::kStateDisconnecting; + } + } + } + } + + LOG(INFO) << __func__ << ": ase_ops size: " << ase_ops.size(); + + // send consolidated release command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Release op"; + ascs_client->Release(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + + tracker_.TransitionTo(StreamTracker::kStateDisconnecting); + + } break; + case PACS_CONNECTION_STATE_EVT: { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + LOG(WARNING) << __func__ + <<": pacs_state: " << static_cast(pacs_state->state); + if(pacs_state->state == ConnectionState::CONNECTED) { + // now send the PACS discovery + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::NONE; + pacs_client->StartDiscovery(pacs_client_id, strm_mgr_->GetAddress()); + } else if(pacs_state->state == ConnectionState::DISCONNECTED) { + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::NONE; + tracker_.HandleInternalDisconnect(false); + return false; + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(pacs_state->state == ConnectionState::CONNECTED) { + context->connection_state = IntConnectState::PACS_DISCOVERING; + } + } + } break; + + case PACS_DISCOVERY_RES_EVT: { + PacsDiscovery pacs_discovery_ = *((PacsDiscovery *) p_data); + GattState ascs_state = strm_mgr_->GetAscsState(); + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + bool process_pacs_results = false; + + // check if this tracker already passed the pacs discovery stage + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(context->connection_state == IntConnectState::PACS_DISCOVERING) { + process_pacs_results = true; + break; + } + } + + if(!process_pacs_results) break; + + bool context_supported = false; + // check the status + if(pacs_discovery_.status) { + tracker_.HandleInternalDisconnect(false); + LOG(ERROR) << __func__ << " PACS discovery failed"; + return false; + } + + tracker_.UpdatePacsDiscovery(pacs_discovery_); + + // Derive the device type based on pacs discovery results + DeriveDeviceType((PacsDiscovery *) p_data); + + // check if supported audio contexts has required contexts + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType stream = it->stream_type; + if(stream.direction == ASE_DIRECTION_SINK) { + if(stream.audio_context & pacs_discovery_.supported_contexts) { + context_supported = true; + } + } else if(stream.direction == ASE_DIRECTION_SRC) { + if((static_cast(stream.audio_context) << 16) & + pacs_discovery_.supported_contexts) { + context_supported = true; + } + } + } + + if(!context_supported) { + LOG(ERROR) << __func__ << " No Matching Supported Contexts found"; + tracker_.HandleInternalDisconnect(false); + break; + } + + // if not present send the BAP callback as disconnected + // compare the codec configs from upper layer to remote dev + // sink or src PACS records/capabilities. + + // go for ASCS discovery only when codec configs are decided + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + // TODO for now will pick directly first set of Codec and QOS configs + + uint8_t index = tracker_.ChooseBestCodec(conn_stream.stream_type, + &conn_stream.codec_qos_config_pair, + &pacs_discovery_); + if(index != 0XFF) { + CodecQosConfig entry = conn_stream.codec_qos_config_pair.at(index); + CodecConfig codec_config = entry.codec_config; + QosConfig qos_config = entry.qos_config; + + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + for (auto ascs_config = qos_config.ascs_configs.begin(); + ascs_config != qos_config.ascs_configs.end(); ascs_config++) { + int_strm_trackers_.FindOrAddBytrackerType(conn_stream.stream_type, + 0x00, ascs_config->cig_id, + ascs_config->cis_id, + codec_config, qos_config); + } + context->codec_config = codec_config; + context->req_qos_config = qos_config; + } else { + LOG(ERROR) << __func__ << " No Matching Codec Found For Stream"; + } + } + + // check if any match between upper layer codec and remote dev's + // pacs records + if(!int_strm_trackers_.size()) { + LOG(WARNING) << __func__ << "No Matching codec found for all streams"; + tracker_.HandleInternalDisconnect(false); + return false; + } + + if(ascs_state == GattState::CONNECTED) { + LOG(WARNING) << __func__ << ": Going For ASCS Service Discovery"; + // now send the ASCS discovery + ascs_client->StartDiscovery(ASCS_CLIENT_ID, strm_mgr_->GetAddress()); + } else if(ascs_state == GattState::DISCONNECTED) { + LOG(WARNING) << __func__ << ": Going For ASCS Conneciton"; + ascs_client->Connect(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), false); + if(gatt_pending_data->ascs_pending_cmd == GattPendingCmd::NONE) { + gatt_pending_data->ascs_pending_cmd = + GattPendingCmd::GATT_CONN_PENDING; + } + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(ascs_state == GattState::CONNECTED) { + context->connection_state = IntConnectState::ASCS_DISCOVERING; + } else if(ascs_state == GattState::DISCONNECTED) { + context->connection_state = IntConnectState::ASCS_CONNECTING; + } + } + } break; + + case ASCS_CONNECTION_STATE_EVT: { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + if(ascs_state->state == GattState::CONNECTED) { + LOG(INFO) << __func__ << " ASCS server connected"; + // now send the ASCS discovery + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::NONE; + ascs_client->StartDiscovery(ASCS_CLIENT_ID, strm_mgr_->GetAddress()); + } else if(ascs_state->state == GattState::DISCONNECTED) { + LOG(INFO) << __func__ << " ASCS server Disconnected"; + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::NONE; + tracker_.HandleInternalDisconnect(false); + return false; + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(ascs_state->state == GattState::CONNECTED) { + context->connection_state = IntConnectState::ASCS_DISCOVERING; + } + } + } break; + case ASCS_DISCOVERY_RES_EVT: { + AscsDiscovery ascs_discovery_ = *((AscsDiscovery *) p_data); + std::vector ase_ops; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + std::vector sink_ase_list = ascs_discovery_.sink_ases_list; + std::vector src_ase_list = ascs_discovery_.src_ases_list; + // check the status + if(ascs_discovery_.status) { + tracker_.HandleInternalDisconnect(false); + return false; + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + context->connection_state = IntConnectState::ASCS_DISCOVERED; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // create the UcastAudioStream for each ASEs (ase id) + // check if the entry is present, if not create and add it to list + // find out number of ASEs which are in IDLE state + for (auto & ase : sink_ase_list) { + audio_strms->FindOrAddByAseId(ase.ase_id, + ase.ase_state, ASE_DIRECTION_SINK); + } + + for (auto & ase : src_ase_list) { + audio_strms->FindOrAddByAseId(ase.ase_id, + ase.ase_state, ASE_DIRECTION_SRC); + } + + LOG(INFO) << __func__ << ": total num of audio strms: " + << audio_strms->size(); + + std::vector sink_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SINK); + + std::vector src_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SRC); + + std::vector state_ids = { StreamTracker::kStateIdle }; + + std::vector idle_sink_streams = + audio_strms->GetStreamsByStates(state_ids, + ASE_DIRECTION_SINK); + std::vector idle_src_streams = + audio_strms->GetStreamsByStates(state_ids, + ASE_DIRECTION_SRC); + LOG(INFO) << __func__ << ": Num of Sink Idle Streams = " + << idle_sink_streams.size() + << ": Num of Src Idle Streams = " + << idle_src_streams.size(); + + LOG(INFO) << __func__ << ": Num of Sink Internal Trackers = " + << sink_int_trackers.size() + << ": Num of Src Internal Trackers = " + << src_int_trackers.size(); + + LOG(INFO) << __func__ << ": Num of Conn Streams " + << loghex(num_conn_streams); + + // check how many stream connections are requested and + // how many streams(ASEs) are available for processing + // check if we have sufficient number of streams(ASEs) for + // the given set of connection requirement + DeviceType dev_type = strm_mgr_->GetDevType(); + uint8_t cis_count = 0; + if(dev_type == DeviceType::EARBUD || + dev_type == DeviceType::HEADSET_STEREO) { + cis_count = 1; + } else if(dev_type == DeviceType::HEADSET_SPLIT_STEREO) { + cis_count = 2; + } + + std::vector valid_state_ids = { + StreamTracker::kStateConnecting, + StreamTracker::kStateConnected, + StreamTracker::kStateStreaming, + StreamTracker::kStateReconfiguring, + StreamTracker::kStateDisconnecting, + StreamTracker::kStateStarting, + StreamTracker::kStateStopping + }; + + if(sink_int_trackers.size()) { + if(idle_sink_streams.size() >= sink_int_trackers.size()) { + AttachStreamsToContext(&sink_int_trackers, &idle_sink_streams, + cis_count, &ase_ops); + } else { + std::vector sink_int_trackers_1, + sink_int_trackers_2; + // split the sink_int_trackers into 2 lists now, one list + // is equal to idle_sink_streams as physical and other + // list as virtually attached + if(idle_sink_streams.size()) { // less num of free ASEs + for (uint8_t i = 0; i < idle_sink_streams.size() ; i++) { + IntStrmTracker *tracker = sink_int_trackers.at(i); + sink_int_trackers_1.push_back(tracker); + } + AttachStreamsToContext(&sink_int_trackers_1, &idle_sink_streams, + cis_count, &ase_ops); + for (uint8_t i = idle_sink_streams.size(); + i < sink_int_trackers.size() ; i++) { + IntStrmTracker *tracker = sink_int_trackers.at(i); + sink_int_trackers_2.push_back(tracker); + } + } + + std::vector all_active_sink_streams = + audio_strms->GetStreamsByStates(valid_state_ids, + ASE_DIRECTION_SINK); + + if(sink_int_trackers_2.size()) { + AttachStreamsToContext(&sink_int_trackers_2, &all_active_sink_streams, + cis_count, &ase_ops); + } else if(sink_int_trackers.size()) { + AttachStreamsToContext(&sink_int_trackers, &all_active_sink_streams, + cis_count, &ase_ops); + } + } + } + + // do the same procedure for src trackers as well + if(src_int_trackers.size()) { + if(idle_src_streams.size() >= src_int_trackers.size()) { + AttachStreamsToContext(&src_int_trackers, &idle_src_streams, + cis_count, &ase_ops); + } else { + std::vector src_int_trackers_1, + src_int_trackers_2; + // split the src_int_trackers into 2 lists now, one list + // is equal to idle_src_streams as physical and other + // list as virtually attached + if(idle_src_streams.size()) { // less num of free ASEs + for (uint8_t i = 0; i < idle_src_streams.size() ; i++) { + IntStrmTracker *tracker = src_int_trackers.at(i); + src_int_trackers_1.push_back(tracker); + } + AttachStreamsToContext(&src_int_trackers_1, &idle_src_streams, + cis_count, &ase_ops); + for (uint8_t i = idle_src_streams.size(); + i < src_int_trackers.size() ; i++) { + IntStrmTracker *tracker = src_int_trackers.at(i); + src_int_trackers_2.push_back(tracker); + } + } + + std::vector all_active_src_streams = + audio_strms->GetStreamsByStates(valid_state_ids, + ASE_DIRECTION_SRC); + + if(src_int_trackers_2.size()) { + AttachStreamsToContext(&src_int_trackers_2, &all_active_src_streams, + cis_count, &ase_ops); + } else if(src_int_trackers.size()) { + AttachStreamsToContext(&src_int_trackers, &all_active_src_streams, + cis_count, &ase_ops); + } + } + } + + // remove all duplicate internal stream trackers + int_strm_trackers_.RemoveVirtualAttachedTrackers(); + + // if the int strm trackers size is 0 then return as + // connected immediately + if(!int_strm_trackers_.size()) { + // update the state to connected + TransitionTo(StreamTracker::kStateConnected); + break; + } + + if(!ase_ops.empty()) { + LOG(WARNING) << __func__ << ": Going For ASCS CodecConfig op"; + ascs_client->CodecConfig(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), + ase_ops); + } else { + tracker_.HandleInternalDisconnect(false); + break; + } + + // refresh the sink and src trackers + sink_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SINK); + + src_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SRC); + + LOG(INFO) << __func__ << ": Num of new Sink Internal Trackers = " + << sink_int_trackers.size() + << ": Num of new Src Internal Trackers = " + << src_int_trackers.size(); + + LOG(INFO) << __func__ << ": Num of new Sink Idle Streams = " + << idle_sink_streams.size() + << ": Num of new Src Idle Streams = " + << idle_src_streams.size(); + + // update the states to connecting or other internal states + if(sink_int_trackers.size()) { + for (uint8_t i = 0; i < sink_int_trackers.size() ; i++) { + UcastAudioStream *stream = idle_sink_streams.at(i); + stream->ase_pending_cmd = AscsPendingCmd::CODEC_CONFIG_ISSUED; + } + } + if(src_int_trackers.size()) { + for (uint8_t i = 0; i < src_int_trackers.size() ; i++) { + UcastAudioStream *stream = idle_src_streams.at(i); + stream->ase_pending_cmd = AscsPendingCmd::CODEC_CONFIG_ISSUED; + } + } + } break; + + case ASCS_ASE_STATE_EVT: { + tracker_.HandleAseStateEvent(p_data, StreamControlType::Connect, + &int_strm_trackers_); + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + + +void StreamTracker::StateConnected::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector stream_configs; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + PacsDiscovery *pacs_discovery_ = tracker_.GetPacsDiscovery(); + StreamControlType control_type = tracker_.GetControlType(); + std::vector conv_streams; + + if(control_type != StreamControlType::Connect && + control_type != StreamControlType::Stop && + control_type != StreamControlType::Reconfig) { + return; + } + + if(control_type == StreamControlType::Connect) { + std::vector *conn_streams = tracker_.GetConnStreams(); + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType type = it->stream_type; + conv_streams.push_back(type); + } + LOG(WARNING) << __func__ << ": Conn Streams Size " << conn_streams->size(); + } else if(control_type == StreamControlType::Reconfig) { + std::vector *reconf_streams = tracker_.GetReconfStreams(); + for (auto it = reconf_streams->begin(); it != reconf_streams->end();it++) { + StreamType type = it->stream_type; + conv_streams.push_back(type); + } + LOG(WARNING) << __func__ << ": Reconfig Streams size " + << reconf_streams->size(); + } else { + conv_streams = *tracker_.GetStreams(); + } + + if(control_type == StreamControlType::Connect || + control_type == StreamControlType::Reconfig) { + for (auto it = conv_streams.begin(); it != conv_streams.end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + UcastAudioStream *stream = audio_strms->FindByStreamType( + (*it).type, (*it).direction); + // avoid duplicate updates + if(context && pacs_discovery_ && + context->stream_state != StreamState::CONNECTED) { + StreamConfigInfo config; + memset(&config, 0, sizeof(config)); + config.stream_type = *it; + if(stream) { + config.codec_config = stream->codec_config; + config.qos_config = stream->qos_config; + context->qos_config = stream->qos_config; + } else { + config.codec_config = context->codec_config; + config.qos_config = context->req_qos_config; + context->qos_config = context->req_qos_config; + } + + //Keeping bits_per_sample as 24 always for LC3 + if (config.codec_config.codec_type == + CodecIndex::CODEC_INDEX_SOURCE_LC3) { + config.codec_config.bits_per_sample = + CodecBPS::CODEC_BITS_PER_SAMPLE_24; + } + + if(config.stream_type.direction == ASE_DIRECTION_SINK) { + config.audio_location = pacs_discovery_->sink_locations; + config.codecs_selectable = pacs_discovery_->sink_pac_records; + } else if(config.stream_type.direction == ASE_DIRECTION_SRC) { + config.audio_location = pacs_discovery_->src_locations; + config.codecs_selectable = pacs_discovery_->src_pac_records; + } + stream_configs.push_back(config); + } + } + + if(stream_configs.size()) { + callbacks->OnStreamConfig(strm_mgr_->GetAddress(), stream_configs); + } + } + + for (auto it = conv_streams.begin(); it != conv_streams.end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + // avoid duplicate updates + if( context->stream_state != StreamState::CONNECTED) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::CONNECTED; + context->stream_state = StreamState::CONNECTED; + strms.push_back(state); + } + } + + if(strms.size()) { + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + } +} + +void StreamTracker::StateConnected::OnExit() { + +} + +bool StreamTracker::StateConnected::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateConnected); + } break; + + case BAP_START_REQ_EVT: { + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + BapStart *evt_data = (BapStart *) p_data; + + tracker_.UpdateControlType(StreamControlType::Start); + + tracker_.UpdateStreams(&evt_data->streams); + + pacs_client->GetAudioAvailability(pacs_client_id, + strm_mgr_->GetAddress()); + + tracker_.TransitionTo(StreamTracker::kStateStarting); + } break; + + case BAP_RECONFIG_REQ_EVT: { + BapReconfig *evt_data = (BapReconfig *) p_data; + + tracker_.UpdateControlType(StreamControlType::Reconfig); + tracker_.UpdateReconfStreams(&evt_data->streams); + + // check if codec reconfiguration or qos reconfiguration + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + // pacs is already connected so initiate + // pacs service discovry now and move the state to reconfiguring + pacs_client->StartDiscovery(pacs_client_id, strm_mgr_->GetAddress()); + + tracker_.TransitionTo(StreamTracker::kStateReconfiguring); + + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + AscsState *ascs = ((AscsState *) p_data); + if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == + ascs::ASE_STATE_CODEC_CONFIGURED) { + tracker_.HandleRemoteReconfig(ASCS_ASE_STATE_EVT, p_data, StateId()); + } + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + + +void StreamTracker::StateStarting::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *start_streams = tracker_.GetStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Start Streams Size: " + << start_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Start) return; + + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::STARTING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::STARTING; + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(*it, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + uint64_t tout = num_ases * + (static_cast(TimeoutVal::StartingTimeout)); + if(!tout) { + tout = static_cast(MaxTimeoutVal::StartingTimeout); + } + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateStartingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateStarting: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateStarting::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateStartingTimer"); +} + +bool StreamTracker::CheckAndUpdateStreamingState( + IntStrmTrackers *int_strm_trackers) { + // to check for all internal trackers are moved to + // streaming state then update it upper layers + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + uint8_t num_strms_in_streaming = 0; + + bool pending_cmds = false; + + // check if any pending commands are present + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cis_pending_cmd != CisPendingCmd::NONE || + stream->ase_pending_cmd != AscsPendingCmd::NONE)) { + LOG(WARNING) << __func__ << ": cis_pending_cmd " + << loghex(static_cast (stream->cis_pending_cmd)); + LOG(WARNING) << __func__ << ": ase_pending_cmd " + << loghex(static_cast (stream->ase_pending_cmd)); + pending_cmds = true; + break; + } + } + + if(pending_cmds) { + LOG(WARNING) << __func__ << ": ASCS/CIS Pending commands left"; + return false; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_state == ascs::ASE_STATE_STREAMING && + stream->cis_state == CisState::ESTABLISHED) { + num_strms_in_streaming++; + } + } + + if(int_strm_trackers->size() != num_strms_in_streaming) { + LOG(WARNING) << __func__ << ": Not all streams moved to streaming"; + return false; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->overall_state = StreamTracker::kStateStreaming; + } + + // all streams are moved to streaming state + TransitionTo(StreamTracker::kStateStreaming); + return true; +} + +bool StreamTracker::StateStarting::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateStarting); + } break; + case BAP_STOP_REQ_EVT: { + tracker_.HandleStop(p_data, StreamTracker::kStateStarting); + } break; + case BAP_STREAM_UPDATE_REQ_EVT: { + BapStreamUpdate *evt_data = (BapStreamUpdate *) p_data; + tracker_.UpdateMetaUpdateStreams(&evt_data->update_streams); + if(tracker_.HandlePacsAudioContextEvent(&pacs_contexts)) { + tracker_.HandleStreamUpdate(StreamTracker::kStateStarting); + } + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case PACS_AUDIO_CONTEXT_RES_EVT: { + // check for all stream start requests, stream contexts are + // part of available contexts + pacs_contexts = *((PacsAvailableContexts *) p_data); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + bool ignore_event = false; + + std::vector *start_streams = tracker_.GetStreams(); + uint8_t contexts_supported = 0; + + // check if supported audio contexts has required contexts + for(auto it = start_streams->begin(); it != start_streams->end(); it++) { + if(it->direction == ASE_DIRECTION_SINK) { + if(it->audio_context & pacs_contexts.available_contexts) { + contexts_supported++; + } + } else if(it->direction == ASE_DIRECTION_SRC) { + if((static_cast(it->audio_context) << 16) & + pacs_contexts.available_contexts) { + contexts_supported++; + } + } + } + + if(contexts_supported != start_streams->size()) { + LOG(ERROR) << __func__ << ": No Matching available Contexts found"; + tracker_.TransitionTo(StreamTracker::kStateConnected); + break; + } + + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr && stream->overall_state == + StreamTracker::kStateStarting) { + ignore_event = true; + break; + } + } + } + + if(ignore_event) break; + + // Now create the groups + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + QosConfig *qos_config = &stream->qos_config; + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + IsoHciStatus status = cis_intf->CreateCig(strm_mgr_->GetAddress(), + false, + qos_config->cig_config, + qos_config->cis_configs); + + LOG(WARNING) << __func__ << ": status: " + << loghex(static_cast(status)); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::CREATED; + stream->cis_state = CisState::READY; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_CREATE_ISSUED; + } else { + LOG(ERROR) << __func__ << " CIG Creation Failed"; + } + } + tracker_.CheckAndSendEnable(&int_strm_trackers_); + } break; + + case CIS_GROUP_STATE_EVT: { + tracker_.HandleCigStateEvent(event, p_data, &int_strm_trackers_); + } break; + + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + // to handle remote driven operations + // check the state and if the state is Enabling + // proceed with cis creation + AscsState *ascs = ((AscsState *) p_data); + + if(!tracker_.ValidateAseUpdate(p_data, &int_strm_trackers_, + StreamTracker::kStateStarting)) { + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + if (ascs->ase_params.ase_state == ascs::ASE_STATE_ENABLING) { + // change the connection state to ENABLING + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + + // check for Enabling notification is received for all ASEs + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + + uint8_t num_enabling_notify = 0; + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_state == ascs::ASE_STATE_ENABLING) { + num_enabling_notify++; + } + } + + if(int_strm_trackers_.size() != num_enabling_notify) { + LOG(WARNING) << __func__ + << "Enabling notification is not received for all strms"; + break; + } + + // As it single group use cases, always single group start request + // will come to BAP layer + IsoHciStatus status; + std::vector cis_ids; + uint8_t cigId; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (std::find(cis_ids.begin(), cis_ids.end(), + stream->cis_id) == cis_ids.end()) { + cis_ids.push_back(stream->cis_id); + cigId = stream->cig_id; + } + } + if(cis_ids.size()) { + LOG(WARNING) << __func__ << ": Going For CIS Creation "; + status = cis_intf->CreateCis(cigId, + cis_ids, + strm_mgr_->GetAddress()); + } + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->cis_retry_count = 0; + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_state = CisState::ESTABLISHED; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + // change the connection state to CIS create issued + stream->cis_pending_cmd = CisPendingCmd::CIS_CREATE_ISSUED; + } else { + LOG(WARNING) << __func__ << "CIS create Failed"; + } + } + } else if (ascs->ase_params.ase_state == ascs::ASE_STATE_STREAMING) { + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + tracker_.HandleRemoteStop(ASCS_ASE_STATE_EVT, p_data, StateId()); + } + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCisId(data->cig_id, data->cis_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << ": Not intended for this tracker"; + break; + } + + if(data->state == CisState::ESTABLISHED) { + // find out the CIS is bidirectional or from air direction + // cis, send Receiver start ready as set up data path + // is already completed during CIG creation + if(data->direction & cis::DIR_FROM_AIR) { + // setup the datapath for RX + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + cis::DIR_FROM_AIR); + LOG(WARNING) << __func__ << " DIR_FROM_AIR " + << loghex(static_cast (cis::DIR_FROM_AIR)); + + if(stream && int_strm_trackers_.FindByAseId(stream->ase_id)) { + LOG(INFO) << __func__ << ": Stream Direction " + << loghex(static_cast (stream->direction)); + + LOG(INFO) << __func__ << ": Stream ASE Id " + << loghex(static_cast (stream->ase_id)); + + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + AseStartReadyOp start_ready_op = { + .ase_id = stream->ase_id + }; + std::vector ase_ops; + ase_ops.push_back(start_ready_op); + ascs_client->StartReady(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->ase_pending_cmd = AscsPendingCmd::START_READY_ISSUED; + } + } + + if(data->direction & cis::DIR_TO_AIR) { + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + cis::DIR_TO_AIR); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + + } else if (data->state == CisState::READY) { // CIS creation failed + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + data->direction); + if(stream && stream->cis_retry_count < 2) { + std::vector cisIds = {stream->cis_id}; + LOG(WARNING) << __func__ << ": Going For Retrial of CIS Creation "; + IsoHciStatus status = cis_intf->CreateCis( + stream->cig_id, + cisIds, + strm_mgr_->GetAddress()); + + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_state = CisState::ESTABLISHED; + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + // change the connection state to CIS create issued + stream->cis_retry_count++; + stream->cis_pending_cmd = CisPendingCmd::CIS_CREATE_ISSUED; + } else { + stream->cis_retry_count = 0; + LOG(WARNING) << __func__ << "CIS create Failed"; + } + } else { + if(stream) { + stream->cis_retry_count = 0; + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + } else { // transient states + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + data->direction); + if(stream) stream->cis_state = data->state; + } + } break; + + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateUpdating::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *update_streams = tracker_.GetMetaUpdateStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Start Streams Size " + << update_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::UpdateStream) return; + + for (auto it = update_streams->begin(); it != update_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = it->stream_type; + state.stream_state = StreamState::UPDATING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::UPDATING; + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(it->stream_type, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::UpdatingTimeout)); + if(!tout) { + tout = static_cast(MaxTimeoutVal::UpdatingTimeout); + } + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateUpdatingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateUpdating: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateUpdating::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateUpdatingTimer"); +} + +bool StreamTracker::StateUpdating::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateUpdating); + } break; + case BAP_STOP_REQ_EVT: { + tracker_.HandleStop(p_data, StreamTracker::kStateUpdating); + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case PACS_AUDIO_CONTEXT_RES_EVT: { + // check for all stream start requests, stream contexts are + // part of available contexts + PacsAvailableContexts *pacs_contexts = (PacsAvailableContexts *) p_data; + if(!tracker_.HandlePacsAudioContextEvent(pacs_contexts) || + !tracker_.HandleStreamUpdate(StreamTracker::kStateUpdating)) { + tracker_.TransitionTo(StreamTracker::kStateStreaming); + } + } break; + case ASCS_ASE_STATE_EVT: { + AscsState *ascs = ((AscsState *) p_data); + if(!tracker_.ValidateAseUpdate(p_data, &int_strm_trackers_, + StreamTracker::kStateUpdating)) { + break; + } + if(ascs->ase_params.ase_state == ascs::ASE_STATE_STREAMING) { + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + tracker_.HandleRemoteStop(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + // this can happen when CIS is lost and detected on remote side + // first so it will immediately transition to QOS configured. + tracker_.HandleAbruptStop(ASCS_ASE_STATE_EVT, p_data); + } + } break; + case CIS_STATE_EVT: { + // handle sudden CIS Disconnection + tracker_.HandleCisEventsInStreaming(p_data); + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateStreaming::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *start_streams = tracker_.GetStreams(); + std::vector *update_streams = tracker_.GetMetaUpdateStreams(); + LOG(WARNING) << __func__ << ": Start Streams Size " + << start_streams->size(); + + LOG(WARNING) << __func__ << ": Update Streams Size " + << update_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type == StreamControlType::Start) { + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::STREAMING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::STREAMING; + } + } else if(control_type == StreamControlType::UpdateStream) { + for (auto it = update_streams->begin(); it != update_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = it->stream_type; + state.stream_state = StreamState::STREAMING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::STREAMING; + } + } + if(strms.size()) { + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + } +} + +void StreamTracker::StateStreaming::OnExit() { + +} + +bool StreamTracker::StateStreaming::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateStreaming); + } break; + case BAP_STOP_REQ_EVT: { + tracker_.HandleStop(p_data, StreamTracker::kStateStreaming); + } break; + case BAP_STREAM_UPDATE_REQ_EVT: { + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + BapStreamUpdate *evt_data = (BapStreamUpdate *) p_data; + tracker_.UpdateControlType(StreamControlType::UpdateStream); + tracker_.UpdateMetaUpdateStreams(&evt_data->update_streams); + pacs_client->GetAudioAvailability(pacs_client_id, + strm_mgr_->GetAddress()); + tracker_.TransitionTo(StreamTracker::kStateUpdating); + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + AscsState *ascs = ((AscsState *) p_data); + uint8_t ase_id = ascs->ase_params.ase_id; + // find out the stream for the given ase id + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + if (stream) { + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + tracker_.HandleRemoteStop(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED){ + // this can happen when CIS is lost and detected on remote side + // first so it will immediately transition to QOS configured. + tracker_.HandleAbruptStop(ASCS_ASE_STATE_EVT, p_data); + } + } + } break; + case CIS_STATE_EVT: { + // handle sudden CIS Disconnection + tracker_.HandleCisEventsInStreaming(p_data); + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateStopping::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *stop_streams = tracker_.GetStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Stop Streams Size : " + << stop_streams->size(); + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Stop) return; + + for (auto it = stop_streams->begin(); + it != stop_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::STOPPING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::STOPPING; + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(*it, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::StoppingTimeout)); + if(!tout) { + tout = static_cast(MaxTimeoutVal::StoppingTimeout); + } + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateStoppingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateStopping: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateStopping::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateStoppingTimer"); +} + +bool StreamTracker::StateStopping::TerminateCisAndCig(UcastAudioStream *stream) { + + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + uint8_t num_strms_in_qos_configured = 0; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + std::vector all_trackers = + int_strm_trackers_.FindByCigIdAndDir(stream->cig_id, + stream->direction); + + for(auto i = all_trackers.begin(); i != all_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + num_strms_in_qos_configured++; + } + } + + if(all_trackers.size() != num_strms_in_qos_configured) { + LOG(WARNING) << __func__ << "Not All Streams Moved to QOS Configured"; + return false; + } + + for (auto i = all_trackers.begin(); i != all_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->cis_pending_cmd == CisPendingCmd::NONE && + stream->cis_state == CisState::ESTABLISHED && + stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + LOG(WARNING) << __func__ << ": Going For CIS Disconnect "; + IsoHciStatus status = cis_intf->DisconnectCis(stream->cig_id, + stream->cis_id, + stream->direction); + if(status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cis_state = CisState::READY; + } else if(status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIS_DESTROY_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIS Disconnect Failed"; + } + } + + if(stream->cis_state == CisState::READY) { + if(stream->cig_state == CigState::CREATED && + stream->cis_pending_cmd == CisPendingCmd::NONE) { + LOG(WARNING) << __func__ << ": Going For CIG Removal"; + IsoHciStatus status = cis_intf->RemoveCig(strm_mgr_->GetAddress(), + stream->cig_id); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_REMOVE_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIG removal Failed"; + } + } + } + } + return true; +} + +bool StreamTracker::StateStopping::CheckAndUpdateStoppedState() { + // to check for all internal trackers are moved to + // cis destroyed state then update the callback + uint8_t num_strms_in_stopping = 0; + bool pending_cmds = false; + + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + // check if any pending commands are present + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cis_pending_cmd != CisPendingCmd::NONE || + stream->ase_pending_cmd != AscsPendingCmd::NONE)) { + pending_cmds = true; + break; + } + } + + if(pending_cmds) return false; + + for(auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cig_state == CigState::IDLE || + stream->cig_state == CigState::INVALID) && + (stream->cis_state == CisState::READY || + stream->cis_state == CisState::INVALID) && + stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + num_strms_in_stopping++; + } + } + + if(int_strm_trackers_.size() != num_strms_in_stopping) { + LOG(WARNING) << __func__ << "Not All Streams Moved to Stopped State"; + return false; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->overall_state = StreamTracker::kStateConnected; + } + + tracker_.TransitionTo(StreamTracker::kStateConnected); + return true; +} + +bool StreamTracker::StateStopping::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT:{ + tracker_.HandleDisconnect(p_data, StreamTracker::kStateStopping); + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + // to handle remote driven operations + AscsState *ascs = ((AscsState *) p_data); + + if(!tracker_.ValidateAseUpdate(p_data, &int_strm_trackers_, + StreamTracker::kStateStopping)) { + break; + } + + // find out the stream for the given ase id + uint8_t ase_id = ascs->ase_params.ase_id; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + break; + } + + if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + if(stream->direction & cis::DIR_FROM_AIR) { + LOG(INFO) << __func__ << " Sending Stop Ready "; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + AseStopReadyOp stop_ready_op = { + .ase_id = stream->ase_id + }; + std::vector ase_ops; + ase_ops.push_back(stop_ready_op); + ascs_client->StopReady(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + stream->ase_pending_cmd = AscsPendingCmd::STOP_READY_ISSUED; + } else { + LOG(ERROR) << __func__ << ": Invalid State transition to Disabling" + << ": ASE Id = " << loghex(ase_id); + } + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + + stream->ase_pending_cmd = AscsPendingCmd::NONE; + // stopped state then issue CIS disconnect + TerminateCisAndCig(stream); + CheckAndUpdateStoppedState(); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCisId(data->cig_id, data->cis_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + if(data->state == CisState::ESTABLISHED) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + if(int_strm_trackers_.FindByAseId(stream->ase_id)) { + TerminateCisAndCig(stream); + } + } + } + } + CheckAndUpdateStoppedState(); + + } else if(data->state == CisState::READY) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + if(stream->cig_state == CigState::CREATED && + stream->cis_pending_cmd == CisPendingCmd::NONE) { + IsoHciStatus status = cis_intf->RemoveCig( + strm_mgr_->GetAddress(), + stream->cig_id); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_REMOVE_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIG removal Failed"; + } + } + } + } + } + CheckAndUpdateStoppedState(); + } else { // transient states + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) stream->cis_state = data->state; + } + } + } + } break; + + case CIS_GROUP_STATE_EVT: { + CisGroupState *data = ((CisGroupState *) p_data); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCigId(data->cig_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CigState::CREATED) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + // check if this is a CIG created event due to CIG create + // issued during starting state + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cig_state = data->state; + stream->cis_state = CisState::READY; + TerminateCisAndCig(stream); + } + } + CheckAndUpdateStoppedState(); + + } else if(data->state == CigState::IDLE) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + CheckAndUpdateStoppedState(); + } + } break; + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +bool StreamTracker::StateDisconnecting::TerminateGattConnection() { + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector *all_contexts = contexts->GetAllContexts(); + bool any_context_active = false; + bool disc_issued = false; + std::vector ids = { StreamTracker::kStateIdle }; + std::vector idle_streams = + audio_strms->GetStreamsByStates( + ids, ASE_DIRECTION_SINK | ASE_DIRECTION_SRC); + + LOG(WARNING) << __func__ <<": Total Streams size: " << audio_strms->size() + <<": Idle Streams size: " << idle_streams.size(); + + // check if any of the contexts stream state is connected + for (auto it = all_contexts->begin(); it != all_contexts->end(); it++) { + if((*it)->stream_state != StreamState::DISCONNECTING && + (*it)->stream_state != StreamState::DISCONNECTED) { + LOG(INFO) << __func__ <<": Other contexts are active,not to disc Gatt"; + any_context_active = true; + break; + } + } + if(!any_context_active && + (!audio_strms->size() || audio_strms->size() == idle_streams.size())) { + + // check if gatt connection can be tear down for ascs & pacs clients + // all streams are in idle state + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + ConnectionState pacs_state = strm_mgr_->GetPacsState(); + if((pacs_state == ConnectionState::CONNECTED && + gatt_pending_data->pacs_pending_cmd == GattPendingCmd::NONE) || + (gatt_pending_data->pacs_pending_cmd == + GattPendingCmd::GATT_CONN_PENDING)) { + LOG(WARNING) << __func__ << " Issue PACS server disconnect "; + pacs_client->Disconnect(pacs_client_id, strm_mgr_->GetAddress()); + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::GATT_DISC_PENDING; + disc_issued = true; + } + + GattState ascs_state = strm_mgr_->GetAscsState(); + if((ascs_state == GattState::CONNECTED && + gatt_pending_data->ascs_pending_cmd == GattPendingCmd::NONE) || + (gatt_pending_data->ascs_pending_cmd == + GattPendingCmd::GATT_CONN_PENDING)) { + LOG(WARNING) << __func__ << " Issue ASCS server disconnect "; + ascs_client->Disconnect(ASCS_CLIENT_ID, strm_mgr_->GetAddress()); + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::GATT_DISC_PENDING; + disc_issued = true; + } + } + return disc_issued; +} + +void StreamTracker::StateDisconnecting::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + // check the previous state i.e connecting, starting, stopping + // or reconfiguring + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + uint8_t num_ases = 0; + + std::vector *disc_streams = tracker_.GetStreams(); + LOG(WARNING) << __func__ << ": Disconection Streams Size: " + << disc_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Disconnect) { + return; + } + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::DISCONNECTING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::DISCONNECTING; + if(context->connection_state == IntConnectState::ASCS_DISCOVERED) { + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + bool can_be_disconnected = tracker_. + StreamCanbeDisconnected(context, id->ase_id); + if(can_be_disconnected) { + int_strm_trackers_.FindOrAddBytrackerType(*it, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + } + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::DisconnectingTimeout)); + if(!tout ||tout > static_cast(MaxTimeoutVal::DisconnectingTimeout)) { + tout = static_cast(MaxTimeoutVal::DisconnectingTimeout); + } + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateDisconnectingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateDisconnecting: Alarm not allocated."; + return; + } + + bool gatt_disc_pending = TerminateGattConnection(); + // check if there are no internal stream trackers, then update to + // upper layer as completely disconnected + if(!int_strm_trackers_.size() && !gatt_disc_pending) { + tracker_.TransitionTo(StreamTracker::kStateIdle); + } +} + +void StreamTracker::StateDisconnecting::ContinueDisconnection + (UcastAudioStream *stream) { + + // check ase state, return if state is not releasing or + if(stream->ase_state != ascs::ASE_STATE_IDLE && + stream->ase_state != ascs::ASE_STATE_CODEC_CONFIGURED && + stream->ase_state != ascs::ASE_STATE_RELEASING) { + LOG(WARNING) << __func__ << " Return as ASE is not moved to Right state"; + return; + } + + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + + // check if there is no pending CIS command then issue relevant + // CIS command based on CIS state + if(stream->cis_pending_cmd != CisPendingCmd::NONE) { + LOG(INFO) << __func__ << ": cis_pending_cmd is not NONE "; + return; + } + + if(stream->cis_state == CisState::ESTABLISHED) { + LOG(WARNING) << __func__ << ": Going For CIS disconnect "; + IsoHciStatus status = cis_intf->DisconnectCis(stream->cig_id, + stream->cis_id, + stream->direction); + if(status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cis_state = CisState::READY; + } else if(status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIS_DESTROY_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIS Disconnect Failed"; + } + } + + if(stream->cis_state == CisState::READY) { + if(stream->cig_state == CigState::CREATED && + stream->cis_pending_cmd == CisPendingCmd::NONE) { + LOG(WARNING) << __func__ << ": Going For CIG Removal"; + IsoHciStatus status = cis_intf->RemoveCig(strm_mgr_->GetAddress(), + stream->cig_id); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_REMOVE_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIG removal Failed"; + } + } + } +} + +bool StreamTracker::StateDisconnecting::CheckAndUpdateDisconnectedState() { + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + bool pending_cmds = false; + + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + + // check if any pending commands are present + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cis_pending_cmd != CisPendingCmd::NONE || + stream->ase_pending_cmd != AscsPendingCmd::NONE)) { + pending_cmds = true; + break; + } + } + + if(pending_cmds) { + LOG(WARNING) << __func__ << " Pending ASCS/CIS cmds present "; + return false; + } + + TerminateGattConnection(); + + // check it needs to wait for ASCS & PACS disconnection also + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + if(gatt_pending_data->ascs_pending_cmd != GattPendingCmd::NONE || + gatt_pending_data->pacs_pending_cmd != GattPendingCmd::NONE) { + LOG(WARNING) << __func__ << " Pending Gatt disc present "; + return false; + } + + // check for all trackers moved to idle and + // CIG state is idle if so update it as streams are disconnected + uint8_t num_strms_disconnected = 0; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if((stream->ase_state == ascs::ASE_STATE_IDLE || + stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED) && + (stream->cig_state == CigState::IDLE || + stream->cig_state == CigState::INVALID) && + (stream->cis_state == CisState::READY || + stream->cis_state == CisState::INVALID)) { + num_strms_disconnected++; + } + } + + if(int_strm_trackers_.size() != num_strms_disconnected) { + LOG(WARNING) << __func__ << "Not disconnected for all streams"; + return false; + } else { + LOG(ERROR) << __func__ << "Disconnected for all streams"; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + stream->overall_state = StreamTracker::kStateIdle; + } + } + + // update the state to idle + tracker_.TransitionTo(StreamTracker::kStateIdle); + return true; +} + +void StreamTracker::StateDisconnecting::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateDisconnectingTimer"); +} + +bool StreamTracker::StateDisconnecting::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case PACS_CONNECTION_STATE_EVT: { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + if(pacs_state->state == ConnectionState::DISCONNECTED) { + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::NONE; + } + CheckAndUpdateDisconnectedState(); + } break; + case ASCS_CONNECTION_STATE_EVT: { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + if(ascs_state->state == GattState::DISCONNECTED) { + // make all streams ASE state to idle so that further processing + // can happen + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::NONE; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector *strms_list = + audio_strms->GetAllStreams(); + + for (auto it = strms_list->begin(); it != strms_list->end(); it++) { + + (*it)->ase_state = ascs::ASE_STATE_IDLE; + (*it)->ase_pending_cmd = AscsPendingCmd::NONE; + (*it)->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(*it); + } + } + CheckAndUpdateDisconnectedState(); + } break; + case ASCS_ASE_STATE_EVT: { // to handle remote driven operations + + // check for state releasing + // based on prev state do accordingly + AscsState *ascs = ((AscsState *) p_data); + + uint8_t ase_id = ascs->ase_params.ase_id; + + // check if current stream tracker is interested in this ASE update + if(int_strm_trackers_.FindByAseId(ase_id) + == nullptr) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(stream == nullptr) { + break; + } else { + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + } + + if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + // find out the stream for the given ase id + LOG(WARNING) << __func__ << " ASE Id " << loghex(ase_id); + stream->ase_pending_cmd = AscsPendingCmd::NONE; + ContinueDisconnection(stream); + + } else if( ascs->ase_params.ase_state == + ascs::ASE_STATE_CODEC_CONFIGURED) { + // check if this is a codec config notification due to codec config + // issued during connecting state + if((tracker_.PreviousStateId() == StreamTracker::kStateConnecting || + tracker_.PreviousStateId() == StreamTracker::kStateReconfiguring) && + stream->ase_pending_cmd == AscsPendingCmd::CODEC_CONFIG_ISSUED && + stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED) { + // mark int conn state as codec configured and issue release command + std::vector ase_ops; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + AseReleaseOp release_op = { + .ase_id = stream->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + ascs_client->Release(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + break; // break the switch case + } else { + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + CheckAndUpdateDisconnectedState(); + } + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_IDLE) { + // check for all trackers moved to idle and + // CIG state is idle if so update it as streams are disconnected + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + CheckAndUpdateDisconnectedState(); + } + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + AscsOpFailed *ascs_op = ((AscsOpFailed *) p_data); + std::vector *ase_list = &ascs_op->ase_list; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + if(ascs_op->ase_op_id == ascs::AseOpId::RELEASE) { + // treat it like internal failure + for (auto i = ase_list->begin(); i != ase_list->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((i)->ase_id); + if(stream) { + stream->ase_state = ascs::ASE_STATE_IDLE; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + } + } + CheckAndUpdateDisconnectedState(); + } + } break; + + case CIS_GROUP_STATE_EVT: { + // check if the associated CIG state is created + // if so go for QOS config operation + CisGroupState *data = ((CisGroupState *) p_data); + + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCigId(data->cig_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CigState::CREATED) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cig_state = data->state; + stream->cis_state = CisState::READY; + // check if this is a CIG created event due to CIG create + // issued during starting state + ContinueDisconnection(stream); + } + } + CheckAndUpdateDisconnectedState(); + + } else if(data->state == CigState::IDLE) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + CheckAndUpdateDisconnectedState(); + } + } break; + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCisId(data->cig_id, data->cis_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + // go for CIS destroy or CIG removal based on CIS state + if(data->state == CisState::ESTABLISHED) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + ContinueDisconnection(stream); + } + } + } + } else if(data->state == CisState::READY) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + ContinueDisconnection(stream); + } + } + } + CheckAndUpdateDisconnectedState(); + } + } break; + + case BAP_TIME_OUT_EVT: { + BapTimeout* timeout = static_cast (p_data); + if (timeout == nullptr) { + LOG(INFO) << __func__ << ": timeout data null, return "; + break; + } + + int stream_tracker_id = timeout->transition_state; + LOG(INFO) << __func__ << ": stream_tracker_ID: " << stream_tracker_id + << ", timeout reason: " << static_cast(timeout->reason); + + std::vector *int_trackers = + int_strm_trackers_.GetTrackerList(); + if (timeout->reason == TimeoutReason::STATE_TRANSITION) { + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + for (auto i = int_trackers->begin(); i != int_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream) { + stream->ase_state = ascs::ASE_STATE_IDLE; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + } + } + CheckAndUpdateDisconnectedState(); + } + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateReconfiguring::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *reconfig_streams = tracker_.GetReconfStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Reconfig Streams Size: " + << reconfig_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Reconfig) return; + + for (auto it = reconfig_streams->begin(); + it != reconfig_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = it->stream_type; + state.stream_state = StreamState::RECONFIGURING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->connection_state = IntConnectState::PACS_DISCOVERING; + context->stream_state = StreamState::RECONFIGURING; + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::ReconfiguringTimeout)); + if(!tout ||tout > static_cast(MaxTimeoutVal::ReconfiguringTimeout)){ + tout = static_cast(MaxTimeoutVal::ReconfiguringTimeout); + } + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateReconfiguringTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": state_transition_timer: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateReconfiguring::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateReconfiguringTimer"); +} + +bool StreamTracker::StateReconfiguring::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + std::vector *reconf_streams = tracker_.GetReconfStreams(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + uint8_t num_reconf_streams = 0; + if(reconf_streams) { + num_reconf_streams = reconf_streams->size(); + } + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateReconfiguring); + } break; + case PACS_DISCOVERY_RES_EVT: { + PacsDiscovery pacs_discovery_ = *((PacsDiscovery *) p_data); + GattState ascs_state = strm_mgr_->GetAscsState(); + uint8_t qos_reconfigs = 0; + + bool process_pacs_results = false; + + // check if this tracker already passed the pacs discovery stage + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if (context->connection_state == IntConnectState::PACS_DISCOVERING) { + context->connection_state = IntConnectState::ASCS_DISCOVERED; + process_pacs_results = true; + } + } + + if(!process_pacs_results) break; + + // check the status + if(pacs_discovery_.status) { + // send the BAP callback as connected as discovery failed + // during reconfiguring + tracker_.TransitionTo(StreamTracker::kStateConnected); + return false; + } + + tracker_.UpdatePacsDiscovery(pacs_discovery_); + + // check if supported audio contexts has required contexts + for (auto it = reconf_streams->begin(); + it != reconf_streams->end();) { + bool context_supported = false; + StreamType stream = it->stream_type; + if(stream.direction == ASE_DIRECTION_SINK) { + if(stream.audio_context & pacs_discovery_.supported_contexts) { + context_supported = true; + } + } else if(stream.direction == ASE_DIRECTION_SRC) { + if((static_cast(stream.audio_context) << 16) & + pacs_discovery_.supported_contexts) { + context_supported = true; + } + } + if(context_supported) { + it++; + } else { + it = reconf_streams->erase(it); + // TODO to update the disconnected callback + } + } + + if(reconf_streams->empty()) { + LOG(ERROR) << __func__ << " No Matching Sup Contexts found"; + LOG(ERROR) << __func__ << " Moving back to Connected state"; + tracker_.TransitionTo(StreamTracker::kStateConnected); + break; + } + + // check physical allocation for all reconfig requests + uint8_t num_phy_attached = 0; + uint8_t num_same_config_applied = 0; + // if not present send the BAP callback as disconnected + // compare the codec configs from upper layer to remote dev + // sink or src PACS records/capabilities. + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + uint8_t index = tracker_.ChooseBestCodec(it->stream_type, + &it->codec_qos_config_pair, + &pacs_discovery_); + if(index != 0XFF) { + CodecQosConfig entry = it->codec_qos_config_pair.at(index); + StreamContext *context = contexts->FindOrAddByType( + it->stream_type); + if(context->attached_state == StreamAttachedState::PHYSICAL) { + num_phy_attached++; + // check if same config is already applied + if(IsCodecConfigEqual(&context->codec_config,&entry.codec_config)) { + num_same_config_applied++; + } + } + } else { + LOG(ERROR) << __func__ << " Matching Codec not found"; + } + } + + if(reconf_streams->size() == num_phy_attached && + num_phy_attached == num_same_config_applied) { + // update the state to connected + LOG(INFO) << __func__ << " Making state to Connected as Nothing to do"; + TransitionTo(StreamTracker::kStateConnected); + break; + } + + if(ascs_state != GattState::CONNECTED) { + break; + } + + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + uint8_t index = tracker_.ChooseBestCodec(it->stream_type, + &it->codec_qos_config_pair, + &pacs_discovery_); + if(index != 0XFF) { + CodecQosConfig entry = it->codec_qos_config_pair.at(index); + StreamContext *context = contexts->FindOrAddByType( + it->stream_type); + CodecConfig codec_config = entry.codec_config; + QosConfig qos_config = entry.qos_config; + + if(context->attached_state == StreamAttachedState::VIRTUAL) { + std::vector phy_attached_contexts; + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + std::vector phy_attached_contexts; + phy_attached_contexts = contexts->FindByAseAttachedState( + id->ase_id, StreamAttachedState::PHYSICAL); + for (auto context_id = phy_attached_contexts.begin(); + context_id != phy_attached_contexts.end(); + context_id++) { + LOG(INFO) << __func__ << ":Attached state made virtual"; + (*context_id)->attached_state = StreamAttachedState::VIRTUAL; + } + } + LOG(INFO) << __func__ << ":Attached state made virtual to phy"; + context->attached_state = StreamAttachedState::VIR_TO_PHY; + context->codec_config = codec_config; + context->req_qos_config = qos_config; + } else if (context->attached_state == StreamAttachedState::PHYSICAL) { + LOG(INFO) << __func__ << ":Attached state is physical"; + // check if same config is already applied + if(IsCodecConfigEqual(&context->codec_config,&entry.codec_config)) { + if(it->reconf_type == StreamReconfigType::CODEC_CONFIG) { + it->reconf_type = StreamReconfigType::QOS_CONFIG; + } + } else { + context->codec_config = codec_config; + } + context->req_qos_config = qos_config; + } + + uint8_t ascs_config_index = 0; + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(it->stream_type, + id->ase_id, + qos_config.ascs_configs[ascs_config_index].cig_id, + qos_config.ascs_configs[ascs_config_index].cis_id, + codec_config, + qos_config); + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->cig_id = id->cig_id = + qos_config.ascs_configs[ascs_config_index].cig_id; + stream->cis_id = id->cis_id = + qos_config.ascs_configs[ascs_config_index].cis_id; + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->codec_config = codec_config; + stream->req_qos_config = qos_config; + stream->qos_config = qos_config; + stream->audio_context = it->stream_type.audio_context; + ascs_config_index++; + } + } else { + LOG(ERROR) << __func__ << " Matching Codec not found"; + } + } + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + if (it->reconf_type == StreamReconfigType::QOS_CONFIG) { + qos_reconfigs++; + } + } + + if(qos_reconfigs == num_reconf_streams) { + // now create the group + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + // check for all streams together so that final group params + // will be decided. + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + tracker_.ChooseBestQos(&stream->req_qos_config, + &stream->pref_qos_params, + &stream->qos_config, + StreamTracker::kStateReconfiguring, + stream->direction); + } + tracker_.CheckAndSendQosConfig(&int_strm_trackers_); + } else { + // now send the ASCS codec config + std::vector ase_ops; + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + if(it->reconf_type == StreamReconfigType::CODEC_CONFIG) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream) { + tracker_.PrepareCodecConfigPayload(&ase_ops, stream); + } + } + } + } + + if(!ase_ops.empty()) { + LOG(WARNING) << __func__ << ": Going For ASCS CodecConfig op"; + ascs_client->CodecConfig(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + } + + // update the states to connecting or other internal states + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + if(it->reconf_type == StreamReconfigType::CODEC_CONFIG) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream) { + stream->ase_pending_cmd = AscsPendingCmd::CODEC_CONFIG_ISSUED; + stream->overall_state = StreamTracker::kStateReconfiguring; + } + } + } else { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream) { + stream->overall_state = StreamTracker::kStateReconfiguring; + } + } + } + } + } + } break; + + case ASCS_ASE_STATE_EVT: { + tracker_.HandleAseStateEvent(p_data, StreamControlType::Reconfig, + &int_strm_trackers_); + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/cc/bta_cc_main.cc b/le_audio/system/bt/bta/cc/bta_cc_main.cc new file mode 100644 index 0000000000000000000000000000000000000000..eedebcc9f3dbc2b10c563b475f1fbe0d0627e52c --- /dev/null +++ b/le_audio/system/bt/bta/cc/bta_cc_main.cc @@ -0,0 +1,2334 @@ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ +/* + * Copyright (C) 2003-2012 Broadcom Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + */ +/****************************************************************************** + * + * This is the main implementation file for the BTA LE audio call Gateway. + * + ******************************************************************************/ + +#include "bta_api.h" +#include "btif_util.h" +#include "bt_target.h" +#include "bta_cc_api.h" +#include "gatts_ops_queue.h" +#include "btm_int.h" +#include "device/include/controller.h" + +#include "osi/include/properties.h" +#include "osi/include/alarm.h" +#include "osi/include/allocator.h" +#include "osi/include/osi.h" +#include "bta_sys.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define MAX_URI_SIZE 50 +#define STANDARD_BEARER_UCI "un000" +#define MS_IN_SEC 1000 +#define CCS_DEFAULT_INDEX_VAL 0 +#define DEFAULT_INDICIES_COUNT 1 + +using bluetooth::Uuid; +using bluetooth::bap::GattsOpsQueue; +class CallControllerImpl; +static CallControllerImpl *cc_instance; +static bool gIsTerminatedInitiatedFromClient = false; +static int gTerminateIntiatedIndex = 0; +static bool gIsActiveCC = false; + +//GTBS UUID (4B: TBS, 4C: GTBS) +Uuid CALL_CONTROL_SERVER_UUID = Uuid::FromString("0000184C-0000-1000-8000-00805F9B34FB"); + +Uuid GTBS_CALL_BEARER_NAME_UUID = Uuid::FromString("00002bb3-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_UCI = Uuid::FromString("00002bb4-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_TECHNOLOGY = Uuid::FromString("00002bb5-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_URI_SCHEMES = Uuid::FromString("00002bb6-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_SIGNAL_STRENGTH = Uuid::FromString("00002bb7-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_SIGNAL_STRENGTH_REPORTINTERVAL = Uuid::FromString("00002bb8-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_LIST_CURRENT_CALLS = Uuid::FromString("00002bb9-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CONTENT_CONTROLID = Uuid::FromString("00002bba-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_STATUS_FLAGS = Uuid::FromString("00002bbb-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_INCOMINGCALL_TARGET_URI = Uuid::FromString("00002bbc-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_STATE_UUID = Uuid::FromString("00002bbd-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_CONTROL_POINT_OPS = Uuid::FromString("00002bbe-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_CONTROL_POINT_OPTIONAL_OPS = Uuid::FromString("00002bbf-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_TERMINATION_REASON = Uuid::FromString("00002bc0-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_INCOMING_CALL = Uuid::FromString("00002bc1-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_FRIENDLY_NAME = Uuid::FromString("00002bc2-0000-1000-8000-00805F9B34FB"); + + +Uuid GTBS_DESCRIPTOR_UUID = Uuid::FromString("00002902-0000-1000-8000-00805f9b34fb"); + +//global varibale +CcsControlServiceInfo_t ccsControlServiceInfo; +tCCS_CALL_STATE CallStateInfo; +std::map CallStatelist; +std::map BlccInfolist; +tCCS_CALL_CONTROL_POINT CallControllerOps; +tCCS_CALL_CONTROL_RESPONSE CallControllerResp; +tCCS_BEARER_LIST_CURRENT_CALLS BlccInfo; +tCCS_BEARER_PROVIDER_INFO BearerProviderInfo; +tCCS_CONTENT_CONTROL_ID CcidInfo; +tCCS_STATUS_FLAGS StatusFlags; +tCCS_INCOMING_CALL IncomingCallInfo; +tCCS_INCOMING_CALL_URI IncomingCallTargetUri; +tCCS_TERM_REASON TerminationReason; +tCCS_FRIENDLY_NAME FriendlyName; +tCCS_SUPP_OPTIONAL_OPCODES SupportedOptionalOpcodes; + +void BTCcCback(tBTA_GATTS_EVT event, tBTA_GATTS* param); +void ReverseByteOrder(unsigned char s[], int length); + +typedef base::Callback service)> + OnGtbsServiceAdded; + +static void OnGtbsServiceAddedCb(uint8_t status, int serverIf, + std::vector service); + +const char* bta_cc_event_str(uint32_t event) { + switch (event) { + CASE_RETURN_STR(CCS_NONE_EVENT) + CASE_RETURN_STR(CCS_INIT_EVENT) + CASE_RETURN_STR(CCS_CLEANUP_EVENT) + CASE_RETURN_STR(CCS_CALL_STATE_UPDATE) + CASE_RETURN_STR(CCS_BEARER_NAME_UPDATE) + CASE_RETURN_STR(CCS_BEARER_UCI_UPDATE) + CASE_RETURN_STR(CCS_BEARER_URI_SCHEMES_SUPPORTED) + CASE_RETURN_STR(CCS_UPDATE) + CASE_RETURN_STR(CCS_OPT_OPCODES) + CASE_RETURN_STR(CCS_BEARER_CURRENT_CALL_LIST_UPDATE) + CASE_RETURN_STR(CCS_BEARER_SIGNAL_STRENGTH_UPDATE) + CASE_RETURN_STR(CCS_SIGNAL_STRENGTH_REPORT_INTERVAL) + CASE_RETURN_STR(CCS_STATUS_FLAGS_UPDATE) + CASE_RETURN_STR(CCS_INCOMING_CALL_UPDATE) + CASE_RETURN_STR(CCS_INCOMING_TARGET_URI_UPDATE) + CASE_RETURN_STR(CCS_TERMINATION_REASON_UPDATE) + CASE_RETURN_STR(CCS_BEARER_TECHNOLOGY_UPDATE) + CASE_RETURN_STR(CCS_CCID_UPDATE) + CASE_RETURN_STR(CCS_ACTIVE_DEVICE_UPDATE) + CASE_RETURN_STR(CCS_CALL_CONTROL_RESPONSE) + CASE_RETURN_STR(CCS_NOTIFY_ALL) + CASE_RETURN_STR(CCS_WRITE_RSP) + CASE_RETURN_STR(CCS_READ_RSP) + CASE_RETURN_STR(CCS_DESCRIPTOR_WRITE_RSP) + CASE_RETURN_STR(CCS_DESCRIPTOR_READ_RSP) + CASE_RETURN_STR(CCS_CONNECTION) + CASE_RETURN_STR(CCS_DISCONNECTION) + CASE_RETURN_STR(CCS_CONNECTION_UPDATE) + CASE_RETURN_STR(CCS_CONGESTION_UPDATE) + CASE_RETURN_STR(CCS_PHY_UPDATE) + CASE_RETURN_STR(CCS_MTU_UPDATE) + CASE_RETURN_STR(CCS_SET_ACTIVE_DEVICE) + CASE_RETURN_STR(CCS_CONNECTION_CLOSE_EVENT) + CASE_RETURN_STR(CCS_BOND_STATE_CHANGE_EVENT) + default: + return (char*)"Unknown bta cc event"; + } +} + + +class CallControllerDevices { + private: + CallActiveDevice activeDevice; + //int max_connection; + public: + bool Add(CallControllerDeviceList device) { + if (devices.size() == MAX_CCS_CONNECTION) { + return false; + } + if (FindByAddress(device.peer_bda) != nullptr) return false; + + device.DeviceStateHandlerPointer[CCS_DISCONNECTED] = DeviceStateDisconnectedHandler; + device.DeviceStateHandlerPointer[CCS_CONNECTED] = DeviceStateConnectionHandler; + device.signal_strength_report_interval = 0; + std::string alarmName = device.peer_bda.ToString(); + alarmName.append("-CC_SSReportingTimer"); + + device.signal_strength_reporting_timer = alarm_new_periodic(alarmName.c_str()); + devices.push_back(device); + return true; + } + + void Remove(RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->peer_bda != address) { + ++it; + continue; + } + if (it == devices.end()) { + LOG(ERROR) << __func__ <<"no matching device"; + return; + } + //Cancel SSReporting timer + if (it->signal_strength_report_interval != 0 && + it->signal_strength_reporting_timer != nullptr) { + alarm_cancel(it->signal_strength_reporting_timer); + alarm_free(it->signal_strength_reporting_timer); + } + + it = devices.erase(it); + + return; + } + } + + void RemoveDevices() { + for (auto it = devices.begin(); it != devices.end();) { + it = devices.erase(it); + } + return; + } + + CallControllerDeviceList* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const CallControllerDeviceList& device) { + return device.peer_bda == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + CallControllerDeviceList* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const CallControllerDeviceList& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector GetRemoteDevices() { + return devices; + } + std::vector FindNotifyDevices(uint16_t handle) { + std::vector notify_devices; + for (size_t it = 0; it != devices.size(); it++){ + if(ccsControlServiceInfo.bearer_provider_name_handle == handle && + devices[it].bearer_provider_name_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.bearer_technology_handle == handle && + devices[it].bearer_technology_changed_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.bearer_signal_strength_handle == handle && + devices[it].bearer_signal_strength_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.bearer_list_currentcalls_handle == handle && + devices[it].bearer_current_calls_list_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_status_flags_handle == handle && + devices[it].status_flags_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.incoming_call_target_beareruri_handle == handle && + devices[it].incoming_call_target_URI_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_state_handle == handle && + devices[it].call_state_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_control_point_handle == handle && + devices[it].call_control_point_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_termination_reason_handle == handle && + devices[it].call_termination_reason_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.incoming_call_handle == handle && + devices[it].incoming_call_state_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_friendly_name_handle == handle && + devices[it].call_friendly_name_notify) { + notify_devices.push_back(devices[it]); + } + } + return notify_devices; + } + void AddSetActiveDevice(tCCS_SET_ACTIVE_DEVICE *device) { + if (device->set_id == activeDevice.set_id) { + activeDevice.address.push_back(device->address); + } else { + activeDevice.address.clear(); + activeDevice.set_id = device->set_id; + activeDevice.address.push_back(device->address); + } + } + + bool FindActiveDevice(CallControllerDeviceList *remoteDevice) { + bool flag = false; + for (auto& it : activeDevice.address) { + if(remoteDevice->peer_bda == it) { + flag = true; + break; + } + } + return flag; + } + static void SSReportingTimerExpired(void *data) { + LOG(INFO) << __func__ ; + CallControllerDeviceList* dev = (CallControllerDeviceList*)data; + if (dev == nullptr) { + LOG(ERROR) << __func__ << "no valid dev handle"; + return; + } + + //send Notification for Signal Strength + tcc_resp_t *rsp = new tcc_resp_t(); + if (rsp == NULL) { + LOG(ERROR) << __func__ << "Allocation error!"; + return; + } + std::vector _data; + _data.clear(); + rsp->remoteDevice = dev; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_signal_strength_handle; + _data.push_back(BearerProviderInfo.signal); + rsp->oper.CallControllerOp.data = std::move(_data); + //force the Notification on timer expiry + rsp->force = true; + + if (dev->bearer_signal_strength_notify) { + LOG(INFO) << "Caling device state handler for SSReporting"; + dev->DeviceStateHandlerPointer[dev->state](CCS_NOTIFY_ALL, rsp); + } else { + LOG(INFO) << "SSReporting Interval set without Desc write for SSR"; + } + } + + static void SetupSSReportingInterval (CallControllerDeviceList* dev) { + LOG(INFO) << __func__ ; + if (alarm_is_scheduled(dev->signal_strength_reporting_timer)) { + alarm_cancel(dev->signal_strength_reporting_timer); + } + if (dev->signal_strength_report_interval != 0) { + alarm_set(dev->signal_strength_reporting_timer, + (period_ms_t)dev->signal_strength_report_interval*MS_IN_SEC, + SSReportingTimerExpired, + (void*)dev); + } + } + + bool UpdateSSReportingInterval(const RawAddress& address, uint8_t ssrInterval) { + bool ret = false; + CallControllerDeviceList *dev = FindByAddress(address); + + if (dev != nullptr) { + dev->signal_strength_report_interval = ssrInterval; + SetupSSReportingInterval(dev); + ret = true; + } + return ret; + } + + std::vector devices; +}; + + +class CallControllerImpl : public CallController { + bluetooth::call_control::CallControllerCallbacks* callbacks; + Uuid app_uuid; + int max_clients; + bool inband_ring_support; + + public: + CallControllerDevices remoteDevices; + virtual ~CallControllerImpl() = default; + + + CallControllerImpl(bluetooth::call_control::CallControllerCallbacks* callback, + Uuid uuid, int max_ccs_clients, bool inband_ringing_enabled) + :callbacks(callback), + app_uuid(uuid), + max_clients(max_ccs_clients), + inband_ring_support(inband_ringing_enabled) { + // HandleCcsEvent(CCS_INIT_EVENT, &app_uuid); + LOG(INFO) << "max_clients " << max_clients; + if (inband_ring_support) { + StatusFlags.supported_flags = (StatusFlags.supported_flags & 0x0000) | INBAND_RINGTONE_FEATURE_BIT; + } else { + StatusFlags.supported_flags = 0x0000; + } + LOG(INFO) << "CallControllerImpl gatts app register"; + BTA_GATTS_AppRegister(app_uuid, BTCcCback, true); + + } + + void Disconnect(const RawAddress& bd_addr) { + LOG(INFO) << __func__; + tCCS_CONNECTION_CLOSE ConnectClosingOp; + ConnectClosingOp.addr = bd_addr; + HandleCcsEvent(CCS_CONNECTION_CLOSE_EVENT, &ConnectClosingOp); + } + + void SetActiveDevice(const RawAddress& address, int setId) { + LOG(INFO) << __func__ ; + tCCS_SET_ACTIVE_DEVICE SetActiveDeviceOp; + SetActiveDeviceOp.set_id = setId; + SetActiveDeviceOp.address = address; + HandleCcsEvent(CCS_ACTIVE_DEVICE_UPDATE, &SetActiveDeviceOp); + } + + void CallState(int len, std::vector call_state_list) { + tCCS_CALL_STATE CallStateOp; + for (int k=0; k::iterator i = BlccInfolist.find(BlccInfo.call_index); + if (i != BlccInfolist.end()) { + LOG(INFO) << __func__ << " update existing Blcc"; + i->second = BlccInfo; + } else { + BlccInfolist.insert({BlccInfo.call_index, BlccInfo}); + } + std::map::iterator j = CallStatelist.find(CallStateOp.index); + if (j != CallStatelist.end()) { + j->second = CallStateOp; + } else { + CallStatelist.insert({CallStateOp.index, CallStateOp}); + } + //clear the term reason + if (CallStateOp.index == TerminationReason.index) { + TerminationReason.index = 0; + TerminationReason.reason = CC_TERM_INVALID_ORIG_URI; + } + } + } + HandleCcsEvent(CCS_CALL_STATE_UPDATE, &CallStatelist); + HandleCcsEvent(CCS_BEARER_CURRENT_CALL_LIST_UPDATE, &BlccInfolist); + } + + void BearerInfoName(uint8_t* name) { + LOG(INFO) << __func__ << name; + tCCS_BEARER_PROVIDER_INFO BearerProviderOp; + BearerProviderOp.length = strlen((char *)name); + memcpy(BearerProviderOp.name, name, BearerProviderOp.length+1); + HandleCcsEvent(CCS_BEARER_NAME_UPDATE, &BearerProviderOp); + } + + void UpdateBearerTechnology(int tech_type) { + LOG(INFO) << __func__ << " tech type: " <::iterator it; + tCCS_INCOMING_CALL IncomingCall; + int len = strlen((char *)Uri); + memcpy(IncomingCall.incoming_uri, Uri, len+1); + IncomingCall.index = index; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++) { + tCCS_BEARER_LIST_CURRENT_CALLS blccObj = it->second; + if (blccObj.call_index == index) { + memcpy(blccObj.call_uri, Uri, strlen((char *)Uri)+1); + break; + } + } + HandleCcsEvent(CCS_INCOMING_CALL_UPDATE, &IncomingCall); + //update Target URI as well with same Info + UpdateIncomingCallTargetUri(index, Uri); + } + + void UpdateIncomingCallTargetUri(int index, uint8_t* target_uri) { + LOG(INFO) << __func__ << " target_uri: " << target_uri; + tCCS_INCOMING_CALL_URI IncomingcallUri; + int len = strlen((char *)target_uri); + memcpy(IncomingcallUri.incoming_target_uri, target_uri, len+1); + IncomingcallUri.index = index; + HandleCcsEvent(CCS_INCOMING_TARGET_URI_UPDATE, &IncomingcallUri); + } + + void ContentControlId(uint32_t ccid) { + LOG(INFO) << __func__; + tCCS_CONTENT_CONTROL_ID ContentControlIdOp; + ContentControlIdOp.ccid = ccid; + HandleCcsEvent(CCS_CCID_UPDATE, &ContentControlIdOp); + } + + int GetDialingCallIndex() { + int retIndex = 0; + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++) { + tCCS_CALL_STATE obj = it->second; + if (obj.state == CCS_STATE_DIALING || obj.state == CCS_STATE_ALERTING) { + LOG(INFO) << __func__ << " call state match: " << obj.index; + retIndex = obj.index; + break; + } + } + return retIndex; + } + void CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address) { + LOG(INFO) << __func__; + tCCS_CALL_CONTROL_RESPONSE CallControlResponse; + CallControllerResp.opcode = op; + CallControllerResp.response_status = status; + CallControllerResp.remote_address = address; + CallControllerResp.index = index; + if (status == CCS_STATUS_SUCCESS && op == CALL_ORIGINATE) { + //get proper call Index for call orignate status + CallControllerResp.index = GetDialingCallIndex(); + } + HandleCcsEvent(CCS_CALL_CONTROL_RESPONSE, &CallControlResponse); + } + + void CallControlInitializedCallback(uint8_t state) { + LOG(INFO) << __func__ << " state" << state; + callbacks->CallControlInitializedCallback(state); + if (state == 0) { + //Initialize local char values + memcpy(BearerProviderInfo.uci, STANDARD_BEARER_UCI, strlen(STANDARD_BEARER_UCI)); + } + } + + void ConnectionStateCallback(uint8_t state, const RawAddress& address) { + LOG(INFO) << __func__ << " state: " << state; + callbacks->ConnectionStateCallback(state, address); + } + + void CallControlPointChange(uint8_t op, uint8_t* indices, int count, char* uri, const RawAddress& address) { + LOG(INFO) << __func__ << " op: " < uri_data; + if (uri != NULL) { + LOG(INFO) << __func__ <<" uri=" << uri; + uri_data.insert(uri_data.begin(), uri, uri + strlen(uri)); + } + std::vector call_indicies; + for (int i=0; iCallControlCallback(op, call_indicies, count, uri_data, address); + } +}; + +void CallController::CleanUp() { + HandleCcsEvent(CCS_CLEANUP_EVENT, NULL); + delete cc_instance; + cc_instance = nullptr; + } + +CallController* CallController::Get() { + CHECK(cc_instance); + return cc_instance; +} + +void CallController::Initialize(bluetooth::call_control::CallControllerCallbacks* callbacks, + Uuid uuid, int max_ccs_clients, bool inband_ringing_enabled) { + if (cc_instance) { + LOG(ERROR) << "Already initialized!"; + } else { + cc_instance = new CallControllerImpl(callbacks, uuid, max_ccs_clients, inband_ringing_enabled); + } + char activeCC[PROPERTY_VALUE_MAX] = "false"; + if(osi_property_get("persist.vendor.service.bt.activeCC", activeCC, "false") && + !strcmp(activeCC, "true")) { + gIsActiveCC = true; + } +} + +bool CallController::IsCcServiceRunnig() { return cc_instance; } + +static std::vector CcAddService(int server_if) { + + std::vector ccs_services; + ccs_services.clear(); + //service + btgatt_db_element_t service = {}; + service.uuid = CALL_CONTROL_SERVER_UUID; + service.type = BTGATT_DB_PRIMARY_SERVICE; + ccs_services.push_back(service); + ccsControlServiceInfo.ccs_service_uuid = service.uuid; + + btgatt_db_element_t bearer_provider_name_char = {}; + bearer_provider_name_char.uuid = GTBS_CALL_BEARER_NAME_UUID; + bearer_provider_name_char.type = BTGATT_DB_CHARACTERISTIC; + bearer_provider_name_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + bearer_provider_name_char.permissions = GATT_PERM_READ; + ccs_services.push_back(bearer_provider_name_char); + ccsControlServiceInfo.bearer_provider_name_uuid = bearer_provider_name_char.uuid; + + btgatt_db_element_t bearer_provider_name_desc = {}; + bearer_provider_name_desc.uuid = GTBS_DESCRIPTOR_UUID; + bearer_provider_name_desc.type = BTGATT_DB_DESCRIPTOR; + bearer_provider_name_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(bearer_provider_name_desc); + + btgatt_db_element_t bearer_technology_char = {}; + bearer_technology_char.uuid = GTBS_BEARER_TECHNOLOGY; + bearer_technology_char.type = BTGATT_DB_CHARACTERISTIC; + bearer_technology_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + bearer_technology_char.permissions = GATT_PERM_READ; + ccs_services.push_back(bearer_technology_char); + ccsControlServiceInfo.bearer_technology_uuid = bearer_technology_char.uuid; + + btgatt_db_element_t bearer_technology_desc = {}; + bearer_technology_desc.uuid = GTBS_DESCRIPTOR_UUID; + bearer_technology_desc.type = BTGATT_DB_DESCRIPTOR; + bearer_technology_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(bearer_technology_desc); + + btgatt_db_element_t gtbs_cc_optional_opcode_char = {}; + gtbs_cc_optional_opcode_char.uuid = GTBS_CALL_CONTROL_POINT_OPTIONAL_OPS; + gtbs_cc_optional_opcode_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_cc_optional_opcode_char.properties = GATT_CHAR_PROP_BIT_READ; + gtbs_cc_optional_opcode_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_cc_optional_opcode_char); + ccsControlServiceInfo.call_control_point_opcode_supported_uuid = gtbs_cc_optional_opcode_char.uuid; + + btgatt_db_element_t gtbs_call_state_char = {}; + gtbs_call_state_char.uuid = GTBS_CALL_STATE_UUID; + gtbs_call_state_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_state_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_call_state_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_call_state_char); + ccsControlServiceInfo.call_state_uuid = gtbs_call_state_char.uuid; + + btgatt_db_element_t gtbs_call_state_desc = {}; + gtbs_call_state_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_state_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_state_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_state_desc); + + btgatt_db_element_t gtbs_call_control_point_char = {}; + gtbs_call_control_point_char.uuid = GTBS_CALL_CONTROL_POINT_OPS; + gtbs_call_control_point_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_control_point_char.properties = GATT_CHAR_PROP_BIT_WRITE|GATT_CHAR_PROP_BIT_NOTIFY|GATT_CHAR_PROP_BIT_WRITE_NR; + gtbs_call_control_point_char.permissions = GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_control_point_char); + ccsControlServiceInfo.call_control_point_uuid = gtbs_call_control_point_char.uuid; + + btgatt_db_element_t gtbs_call_control_point_desc = {}; + gtbs_call_control_point_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_control_point_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_control_point_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_control_point_desc); + + btgatt_db_element_t gtbs_bearer_uci_char = {}; + gtbs_bearer_uci_char.uuid = GTBS_BEARER_UCI; + gtbs_bearer_uci_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_bearer_uci_char.properties = GATT_CHAR_PROP_BIT_READ; + gtbs_bearer_uci_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_bearer_uci_char); + ccsControlServiceInfo.bearer_uci_uuid= gtbs_bearer_uci_char.uuid; + + btgatt_db_element_t gtbs_bearer_URI_schemes_char = {}; + gtbs_bearer_URI_schemes_char.uuid = GTBS_BEARER_URI_SCHEMES; + gtbs_bearer_URI_schemes_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_bearer_URI_schemes_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_bearer_URI_schemes_char.permissions = GATT_PERM_READ; + + ccs_services.push_back(gtbs_bearer_URI_schemes_char); + ccsControlServiceInfo.bearer_uri_schemes_supported_uuid = gtbs_bearer_URI_schemes_char.uuid; + + btgatt_db_element_t gtbs_bearer_URI_schemes_desc = {}; + gtbs_bearer_URI_schemes_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_bearer_URI_schemes_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_bearer_URI_schemes_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_bearer_URI_schemes_desc); + + btgatt_db_element_t gtbs_signal_strength_char = {}; + gtbs_signal_strength_char.uuid = GTBS_SIGNAL_STRENGTH; + gtbs_signal_strength_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_signal_strength_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_signal_strength_char.permissions = GATT_PERM_READ; + + ccs_services.push_back(gtbs_signal_strength_char); + ccsControlServiceInfo.bearer_signal_strength_uuid = gtbs_signal_strength_char.uuid; + + btgatt_db_element_t gtbs_signal_strength_desc = {}; + gtbs_signal_strength_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_signal_strength_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_signal_strength_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_signal_strength_desc); + + btgatt_db_element_t gtbs_signal_strength_report_interval_char = {}; + gtbs_signal_strength_report_interval_char.uuid = GTBS_SIGNAL_STRENGTH_REPORTINTERVAL; + gtbs_signal_strength_report_interval_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_signal_strength_report_interval_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_WRITE|GATT_CHAR_PROP_BIT_WRITE_NR; + gtbs_signal_strength_report_interval_char.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_signal_strength_report_interval_char); + ccsControlServiceInfo.bearer_signal_strength_report_interval_uuid = gtbs_signal_strength_report_interval_char.uuid; + + btgatt_db_element_t gtbs_list_current_calls_char = {}; + gtbs_list_current_calls_char.uuid = GTBS_BEARER_LIST_CURRENT_CALLS; + gtbs_list_current_calls_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_list_current_calls_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_list_current_calls_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_list_current_calls_char); + ccsControlServiceInfo.bearer_list_currentcalls_uuid = gtbs_list_current_calls_char.uuid; + + btgatt_db_element_t gtbs_list_current_calls_desc = {}; + gtbs_list_current_calls_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_list_current_calls_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_list_current_calls_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_list_current_calls_desc); + + btgatt_db_element_t gtbs_call_status_flags_char = {}; + gtbs_call_status_flags_char.uuid = GTBS_CALL_STATUS_FLAGS; + gtbs_call_status_flags_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_status_flags_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_call_status_flags_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_call_status_flags_char); + ccsControlServiceInfo.call_status_flags_uuid = gtbs_call_status_flags_char.uuid; + + btgatt_db_element_t gtbs_call_status_flags_desc = {}; + gtbs_call_status_flags_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_status_flags_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_status_flags_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_status_flags_desc); + + btgatt_db_element_t gtbs_incomingcall_target_bearer_URI_char = {}; + gtbs_incomingcall_target_bearer_URI_char.uuid = GTBS_INCOMINGCALL_TARGET_URI; + gtbs_incomingcall_target_bearer_URI_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_incomingcall_target_bearer_URI_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_incomingcall_target_bearer_URI_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_incomingcall_target_bearer_URI_char); + ccsControlServiceInfo.incoming_call_target_beareruri_uuid = gtbs_incomingcall_target_bearer_URI_char.uuid; + + btgatt_db_element_t gtbs_incomingcall_target_bearer_URI_desc = {}; + gtbs_incomingcall_target_bearer_URI_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_incomingcall_target_bearer_URI_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_incomingcall_target_bearer_URI_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_incomingcall_target_bearer_URI_desc); + + btgatt_db_element_t gtbs_incomingcall_char = {}; + gtbs_incomingcall_char.uuid = GTBS_INCOMING_CALL; + gtbs_incomingcall_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_incomingcall_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_incomingcall_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_incomingcall_char); + ccsControlServiceInfo.incoming_call_uuid = gtbs_incomingcall_char.uuid; + + btgatt_db_element_t gtbs_incomingcall_desc = {}; + gtbs_incomingcall_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_incomingcall_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_incomingcall_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_incomingcall_desc); + + btgatt_db_element_t gtbs_call_termination_reason_char = {}; + gtbs_call_termination_reason_char.uuid = GTBS_CALL_TERMINATION_REASON; + gtbs_call_termination_reason_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_termination_reason_char.properties = GATT_CHAR_PROP_BIT_NOTIFY; + ccs_services.push_back(gtbs_call_termination_reason_char); + ccsControlServiceInfo.call_termination_reason_uuid = gtbs_call_termination_reason_char.uuid; + + btgatt_db_element_t gtbs_call_termination_reason_desc = {}; + gtbs_call_termination_reason_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_termination_reason_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_termination_reason_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_termination_reason_desc); + + btgatt_db_element_t gtbs_call_friendly_name_char = {}; + gtbs_call_friendly_name_char.uuid = GTBS_CALL_FRIENDLY_NAME; + gtbs_call_friendly_name_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_friendly_name_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_call_friendly_name_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_call_friendly_name_char); + ccsControlServiceInfo.call_friendly_name_uuid = gtbs_call_friendly_name_char.uuid; + + btgatt_db_element_t gtbs_call_friendly_name_desc = {}; + gtbs_call_friendly_name_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_friendly_name_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_friendly_name_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_friendly_name_desc); + + btgatt_db_element_t gtbs_ccid_char = {}; + gtbs_ccid_char.uuid = GTBS_CONTENT_CONTROLID; + gtbs_ccid_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_ccid_char.properties = GATT_CHAR_PROP_BIT_READ; + gtbs_ccid_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_ccid_char); + ccsControlServiceInfo.gtbs_ccid_uuid = gtbs_ccid_char.uuid; + + return ccs_services; +} + +static void OnGtbsServiceAddedCb(uint8_t status, int serverIf, + std::vector service) { + + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << "%s: Attempt to register restricted service"<< __func__; + return; + } + + for(int i=0; i< (int)service.size(); i++) { + + if (service[i].uuid == CALL_CONTROL_SERVER_UUID) { + LOG(INFO) << __func__ << " GTBS service added attr handle: " << service[i].attribute_handle; + } else if(service[i].uuid == GTBS_CALL_BEARER_NAME_UUID) { + ccsControlServiceInfo.bearer_provider_name_handle = service[i++].attribute_handle; + ccsControlServiceInfo.bearer_provider_name_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_provider_name_attr: " + << ccsControlServiceInfo.bearer_provider_name_handle + << " bearer_provider_name_desc: " + << ccsControlServiceInfo.bearer_provider_name_desc; + } else if(service[i].uuid == GTBS_BEARER_TECHNOLOGY) { + ccsControlServiceInfo.bearer_technology_handle = service[i++].attribute_handle; + ccsControlServiceInfo.bearer_technology_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_technology_handle: " + << ccsControlServiceInfo.bearer_technology_handle + << " bearer_technology_desc: " + << ccsControlServiceInfo.bearer_technology_desc; + } else if(service[i].uuid == GTBS_CALL_CONTROL_POINT_OPTIONAL_OPS) { + ccsControlServiceInfo.call_control_point_opcode_supported_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << " call_control_point_opcode_supported_handle register: " + << ccsControlServiceInfo.call_control_point_opcode_supported_handle; + } else if (service[i].uuid == GTBS_CALL_STATE_UUID) { + + ccsControlServiceInfo.call_state_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_state_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_state_handle: " + << ccsControlServiceInfo.call_state_handle + << " call_state_handle desc: " + << ccsControlServiceInfo.call_state_desc; + } else if(service[i].uuid == GTBS_CALL_CONTROL_POINT_OPS) { + ccsControlServiceInfo.call_control_point_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_control_point_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_control_point_handle: " + << ccsControlServiceInfo.call_control_point_handle + << " call_control_point_desc: " + << ccsControlServiceInfo.call_control_point_desc; + } else if(service[i].uuid == GTBS_BEARER_UCI) { + ccsControlServiceInfo.bearer_uci_handle = service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_uci_handle: " + << ccsControlServiceInfo.bearer_uci_handle; + + } else if(service[i].uuid == GTBS_BEARER_URI_SCHEMES) { + ccsControlServiceInfo.bearer_uri_schemes_supported_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.bearer_uri_schemes_supported_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_uri_schemes_supported_handle: " + << ccsControlServiceInfo.bearer_uri_schemes_supported_handle + << " bearer_uri_schemes_supported_desc: " + << ccsControlServiceInfo.bearer_uri_schemes_supported_desc; + + } else if(service[i].uuid == GTBS_SIGNAL_STRENGTH) { + ccsControlServiceInfo.bearer_signal_strength_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.bearer_signal_strength_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_signal_strength_handle: " + << ccsControlServiceInfo.bearer_signal_strength_handle + << " bearer_signal_strength_desc: " + << ccsControlServiceInfo.bearer_signal_strength_desc; + + } else if(service[i].uuid == GTBS_SIGNAL_STRENGTH_REPORTINTERVAL) { + ccsControlServiceInfo.bearer_signal_strength_report_interval_handle = + service[i].attribute_handle; + + LOG(INFO) << __func__ << " bearer_signal_strength_report_interval_handle: " + << ccsControlServiceInfo.bearer_signal_strength_report_interval_handle; + + } else if(service[i].uuid == GTBS_BEARER_LIST_CURRENT_CALLS) { + ccsControlServiceInfo.bearer_list_currentcalls_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.bearer_list_currentcalls_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_list_currentcalls_handle: " + << ccsControlServiceInfo.bearer_list_currentcalls_handle + << " bearer_list_currentcalls_desc: " + << ccsControlServiceInfo.bearer_list_currentcalls_desc; + + } else if(service[i].uuid == GTBS_CALL_STATUS_FLAGS) { + ccsControlServiceInfo.call_status_flags_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_status_flags_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_status_flags_handle: " + << ccsControlServiceInfo.call_status_flags_handle + << " call_status_flags_desc: " + << ccsControlServiceInfo.call_status_flags_desc; + + } else if(service[i].uuid == GTBS_INCOMINGCALL_TARGET_URI) { + ccsControlServiceInfo.incoming_call_target_beareruri_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.incoming_call_target_bearerURI_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " incoming_call_target_beareruri_handle: " + << ccsControlServiceInfo.incoming_call_target_beareruri_handle + << " incoming_call_target_bearerURI_desc: " + << ccsControlServiceInfo.incoming_call_target_bearerURI_desc; + } else if(service[i].uuid == GTBS_INCOMING_CALL) { + ccsControlServiceInfo.incoming_call_handle = service[i++].attribute_handle; + ccsControlServiceInfo.incoming_call_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " incoming_call_handle: " + << ccsControlServiceInfo.incoming_call_handle + << " incoming_call_desc: " + << ccsControlServiceInfo.incoming_call_desc; + } else if(service[i].uuid == GTBS_CONTENT_CONTROLID) { + ccsControlServiceInfo.ccid_handle = service[i].attribute_handle; + LOG(INFO) << __func__ << " ccid_handle: " << ccsControlServiceInfo.ccid_handle; + //Declare the CC Initialization + cc_instance->CallControlInitializedCallback(0); + } else if(service[i].uuid == GTBS_CALL_TERMINATION_REASON) { + ccsControlServiceInfo.call_termination_reason_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.call_termination_reason_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " call_termination_reason_handle: " + << ccsControlServiceInfo.call_termination_reason_handle + << " call_termination_reason_desc: " + << ccsControlServiceInfo.call_termination_reason_desc; + } else if(service[i].uuid == GTBS_CALL_FRIENDLY_NAME) { + ccsControlServiceInfo.call_friendly_name_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_friendly_name_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_friendly_name_handle: " + << ccsControlServiceInfo.call_friendly_name_handle + << " call_friendly_name_desc: " + << ccsControlServiceInfo.call_friendly_name_desc; + } + } +} + +void PrintData(uint8_t data[], uint16_t len) { + for (int i=0; i _data; + _data.clear(); + uint8_t status = BT_STATUS_SUCCESS; + rsp->event = CCS_NONE_EVENT; + bool isCallControllerOpUsed = false; + switch (event) { + + case CCS_INIT_EVENT: + { + Uuid aap_uuid = bluetooth::Uuid::GetRandom(); + BTA_GATTS_AppRegister(aap_uuid, BTCcCback, true); + break; + } + + case CCS_CLEANUP_EVENT: + { + //unregister APP + BTA_GATTS_AppDeregister(ccsControlServiceInfo.server_if); + cc_instance->remoteDevices.RemoveDevices(); + break; + } + case BTA_GATTS_REG_EVT: + { + p_data = (tBTA_GATTS*)param; + if (p_data->reg_oper.status == BT_STATUS_SUCCESS) { + ccsControlServiceInfo.server_if = p_data->reg_oper.server_if; + std::vector service; + service = CcAddService(ccsControlServiceInfo.server_if); + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << __func__ << " service app register uuid is not valid"; + break; + } + LOG(INFO) << __func__ << " service app register"; + BTA_GATTS_AddService(ccsControlServiceInfo.server_if, service, base::Bind(&OnGtbsServiceAddedCb)); + } + break; + } + + case BTA_GATTS_DEREG_EVT: + { + break; + } + + case BTA_GATTS_CONF_EVT: { + p_data = (tBTA_GATTS*)param; + uint16_t conn_id = p_data->req_data.conn_id; + uint8_t status = p_data->req_data.status; + LOG(INFO) << __func__ << "conn_id :" << conn_id << "status:" << status; + if (status == BT_STATUS_SUCCESS) { + LOG(INFO) << __func__ << "Notification callback for conn_id :" << conn_id; + GattsOpsQueue::NotificationCallback(conn_id); + } + break; + } + + case BTA_GATTS_CONGEST_EVT: + { + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice; + remoteDevice = cc_instance->remoteDevices.FindByConnId(p_data->congest.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id: " + << p_data->congest.conn_id; + break; + } + // rsp->ConngestionOp.status = p_data->req_data.status; + rsp->remoteDevice = remoteDevice; + rsp->oper.CongestionOp.congested = p_data->congest.congested; + rsp->event = CCS_CONGESTION_UPDATE; + break; + } + case BTA_GATTS_MTU_EVT: { + p_data = (tBTA_GATTS*)param; + + CallControllerDeviceList *remoteDevice; + remoteDevice = cc_instance->remoteDevices.FindByConnId(p_data->congest.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id: " + << p_data->congest.conn_id; + break; + } + rsp->event = CCS_MTU_UPDATE; + rsp->remoteDevice = remoteDevice; + rsp->oper.MtuOp.mtu = p_data->req_data.p_data->mtu; + break; + } + case BTA_GATTS_CONNECT_EVT: { + + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " remote devices connected"; + /* + #if (!defined(BTA_SKIP_BLE_START_ENCRYPTION) || BTA_SKIP_BLE_START_ENCRYPTION == FALSE) + btif_gatt_check_encrypted_link(p_data->conn.remote_bda, + p_data->conn.transport); + #endif*/ + CallControllerDeviceList remoteDevice; + memset(&remoteDevice, 0, sizeof(remoteDevice)); + if(cc_instance->remoteDevices.FindByAddress(p_data->conn.remote_bda)) { + LOG(INFO) << __func__ << " remote devices already there is connected list"; + status = BT_STATUS_FAIL; + return; + } + remoteDevice.peer_bda = p_data->conn.remote_bda; + remoteDevice.conn_id = p_data->conn.conn_id; + if(cc_instance->remoteDevices.Add(remoteDevice) == false) { + LOG(INFO) << __func__ << " remote device is not added : max connection reached"; + // need to check disconnection required + break; + } + remoteDevice.state = CCS_DISCONNECTED; + + LOG(INFO) << __func__ << " remote devices connected conn_id: "<< remoteDevice.conn_id << + "bd_addr " << remoteDevice.peer_bda; + + rsp->event = CCS_CONNECTION; + rsp->remoteDevice = cc_instance->remoteDevices.FindByAddress(p_data->conn.remote_bda); + if (rsp->remoteDevice == NULL) { + LOG(INFO)<<__func__ << " remote dev is null"; + break; + } + break; + } + + case BTA_GATTS_DISCONNECT_EVT: { + LOG(INFO) << __func__ << " remote devices disconnected"; + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice; + remoteDevice = cc_instance->remoteDevices.FindByConnId(p_data->conn_update.conn_id); + if((!remoteDevice) ) { + status = BT_STATUS_FAIL; + break; + } + + rsp->remoteDevice->peer_bda = remoteDevice->peer_bda; + rsp->event = CCS_DISCONNECTION; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_STOP_EVT: + //Do nothing + break; + + case BTA_GATTS_DELELTE_EVT: + //Do nothing + break; + + case BTA_GATTS_READ_CHARACTERISTIC_EVT: { + p_data = (tBTA_GATTS*)param; + std::vector value; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + LOG(INFO) << __func__ << " charateristcs read handle " << + p_data->req_data.p_data->read_req.handle <<" trans_id : " << + p_data->req_data.trans_id; + + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore read operation"; + status = BT_STATUS_FAIL; + break; + } + + LOG(INFO) <<" offset: " << p_data->req_data.p_data->read_req.offset << + " long : " << p_data->req_data.p_data->read_req.is_long; + + tGATTS_RSP rsp_struct; + rsp_struct.attr_value.auth_req = 0; + rsp_struct.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp_struct.attr_value.offset = p_data->req_data.p_data->read_req.offset; + + if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_state_handle) { + std::vector loc_desc_value; + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++){ + tCCS_CALL_STATE obj = it->second; + loc_desc_value.push_back(obj.index); + loc_desc_value.push_back(obj.state); + loc_desc_value.push_back(obj.flags); + } + size_t count = std::min((size_t)GATT_MAX_ATTR_LEN, loc_desc_value.size()); + rsp_struct.attr_value.len = count; + memcpy(rsp_struct.attr_value.value, loc_desc_value.data(), rsp_struct.attr_value.len); + + + LOG(INFO) << __func__ << " CallStateInfo read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_control_point_opcode_supported_handle) { + + rsp_struct.attr_value.len = sizeof(SupportedOptionalOpcodes.supp_opcode); + memcpy(rsp_struct.attr_value.value, &SupportedOptionalOpcodes.supp_opcode, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " callcontrol_point_opcode_supported_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_provider_name_handle) { + LOG(INFO) << __func__ << " BearerProviderInfo name read " << BearerProviderInfo.name; + rsp_struct.attr_value.len = strlen((char *)BearerProviderInfo.name); + LOG(INFO) << __func__ << " BearerProviderInfo name len: " <req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_list_currentcalls_handle) { + + std::vector loc_desc_value; + std::map::iterator it; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++){ + tCCS_BEARER_LIST_CURRENT_CALLS obj = it->second; + loc_desc_value.push_back(obj.list_length); + loc_desc_value.push_back(obj.call_index); + loc_desc_value.push_back(obj.call_state); + loc_desc_value.push_back(obj.call_flags); + + loc_desc_value.insert(loc_desc_value.end(), + obj.call_uri, obj.call_uri+(obj.list_length-3)); + } + size_t count = std::min((size_t)GATT_MAX_ATTR_LEN, loc_desc_value.size()); + rsp_struct.attr_value.len = count; + memcpy(rsp_struct.attr_value.value, loc_desc_value.data(), rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " BlccInfo read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_technology_handle) { + rsp_struct.attr_value.len = sizeof(BearerProviderInfo.technology_type); + memcpy(rsp_struct.attr_value.value, &BearerProviderInfo.technology_type, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " technology_type read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uci_handle) { + rsp_struct.attr_value.len = strlen((char *)BearerProviderInfo.uci); + memcpy(rsp_struct.attr_value.value, BearerProviderInfo.uci, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Bearer UCI read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_handle) { + rsp_struct.attr_value.len = sizeof(BearerProviderInfo.signal); + memcpy(rsp_struct.attr_value.value, &BearerProviderInfo.signal, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " signal strength read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_report_interval_handle) { + rsp_struct.attr_value.len = sizeof(BearerProviderInfo.signal_report_interval); + memcpy(rsp_struct.attr_value.value, &BearerProviderInfo.signal_report_interval, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " signal_report_interval read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uri_schemes_supported_handle) { + rsp_struct.attr_value.len = strlen((const char*)BearerProviderInfo.bearer_schemes_list); + memcpy(rsp_struct.attr_value.value, BearerProviderInfo.bearer_schemes_list, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " bearer_schemes_list read"; + }else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.incoming_call_handle) { + rsp_struct.attr_value.len = 1 + strlen((char*)IncomingCallInfo.incoming_uri); + memcpy(rsp_struct.attr_value.value, &IncomingCallInfo, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Incoming call read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.incoming_call_target_beareruri_handle) { + rsp_struct.attr_value.len = 1 + strlen((char*)IncomingCallTargetUri.incoming_target_uri); + memcpy(rsp_struct.attr_value.value, &IncomingCallTargetUri, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Incoming Call target URI read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.ccid_handle) { + rsp_struct.attr_value.len = sizeof(CcidInfo); + memcpy(rsp_struct.attr_value.value, &CcidInfo, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Content Control read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_status_flags_handle) { + rsp_struct.attr_value.len = sizeof(StatusFlags.supported_flags); + memcpy(rsp_struct.attr_value.value, &StatusFlags.supported_flags, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Status flags read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_friendly_name_handle) { + rsp_struct.attr_value.len = 1 + strlen((char*)FriendlyName.name); + memcpy(rsp_struct.attr_value.value, &FriendlyName, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Call friendly name read"; + }else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_termination_reason_handle) { + rsp_struct.attr_value.len = sizeof(TerminationReason); + memcpy(rsp_struct.attr_value.value, &TerminationReason, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Termination Reason read"; + } + else { + LOG(INFO) << __func__ << " read request for unknow handle " << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + LOG(INFO) << __func__ << " read request handle " << p_data->req_data.p_data->read_req.handle << + "connection id " << p_data->req_data.conn_id; + rsp->oper.ReadOp.char_handle = rsp_struct.attr_value.handle; + rsp->oper.ReadOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadOp.status = BT_STATUS_SUCCESS; + rsp->event = CCS_READ_RSP; + rsp->remoteDevice = remoteDevice; + + memcpy((void*)&rsp->rsp_value, &rsp_struct, sizeof(rsp_struct)); + break; + } + + case BTA_GATTS_READ_DESCRIPTOR_EVT: { + LOG(INFO) << __func__ << " read descriptor"; + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " charateristcs read desc handle " << + p_data->req_data.p_data->read_req.handle << " offset : " + << p_data->req_data.p_data->read_req.offset; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_FAIL; + break; + } + uint16_t data = 0x00; + if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_state_desc) { + LOG(INFO) << __func__ << " call_state_desc read"; + data = remoteDevice->call_state_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_control_point_desc) { + LOG(INFO) << __func__ << " callcontrol_point_desc read"; + data = remoteDevice->call_control_point_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_provider_name_desc) { + LOG(INFO) << __func__ << " bearer_provider_name_desc read"; + data = remoteDevice->bearer_provider_name_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_desc) { + LOG(INFO) << __func__ << " bearer_signal_strength desc read"; + data = remoteDevice->bearer_signal_strength_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_list_currentcalls_desc) { + LOG(INFO) << __func__ << " bearer_list_currentcall read"; + data = remoteDevice->bearer_current_calls_list_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_technology_desc) { + LOG(INFO) << __func__ << " bearer_technology_desc read"; + data = remoteDevice->bearer_technology_changed_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uci_desc) { + LOG(INFO) << __func__ << " bearer_uci_desc read"; + data = remoteDevice->bearer_uci_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uri_schemes_supported_desc) { + LOG(INFO) << __func__ << " bearer_uri_schemes_supported_desc read"; + data = remoteDevice->bearer_uri_schemes_supported_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_friendly_name_desc) { + LOG(INFO) << __func__ << " call_friendly_name_desc read"; + data = remoteDevice->call_friendly_name_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_control_point_opcode_supported_desc) { + LOG(INFO) << __func__ << " call_control_point_opcode_supported_desc read"; + data = remoteDevice->call_control_point_opcode_supported_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_termination_reason_desc) { + LOG(INFO) << __func__ << " call_termination_reason_desc read"; + data = remoteDevice->call_termination_reason_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_report_interval_desc) { + LOG(INFO) << __func__ << " bearer_signal_strength_report_interval_desc read"; + data = remoteDevice->bearer_signal_strength_report_interval_notify; + } else { + LOG(INFO) << __func__ << " read request for unknown handle " << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + rsp->rsp_value.attr_value.auth_req = 0; + rsp->rsp_value.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp->rsp_value.attr_value.offset = p_data->req_data.p_data->read_req.offset; + *(uint16_t *)rsp->rsp_value.attr_value.value = (uint16_t)data; + rsp->rsp_value.attr_value.len = sizeof(data); + //cc response + rsp->oper.ReadDescOp.desc_handle = p_data->req_data.p_data->read_req.handle; + rsp->oper.ReadDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadDescOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + rsp->event = CCS_DESCRIPTOR_READ_RSP; + break; + } + + case BTA_GATTS_WRITE_CHARACTERISTIC_EVT: { + + p_data = (tBTA_GATTS*)param; + tGATT_WRITE_REQ req = p_data->req_data.p_data->write_req; + LOG(INFO) << __func__ << " write characteristics len: " << req.len; + PrintData(req.value, req.len); + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_FAIL; + break; + } + if ( status != BT_STATUS_FAIL) { + rsp->oper.WriteOp.char_handle = req.handle; + rsp->oper.WriteOp.trans_id = p_data->req_data.trans_id; + rsp->oper.WriteOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + rsp->oper.WriteOp.need_rsp = req.need_rsp; + rsp->oper.WriteOp.offset = req.offset; + rsp->oper.WriteOp.len = req.len; + rsp->oper.WriteOp.data = (uint8_t*) malloc(sizeof(uint8_t)*req.len); + memcpy(rsp->oper.WriteOp.data, req.value, req.len); + rsp->event = CCS_WRITE_RSP; + } + break; + } + + case BTA_GATTS_WRITE_DESCRIPTOR_EVT: { + + p_data = (tBTA_GATTS* )param; + std::vector write_desc_value; + write_desc_value.clear(); + uint16_t handle = 0; + uint16_t req_value = 0; + const auto& req = p_data->req_data.p_data->write_req; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore notification"; + break; + } + req_value = *(uint16_t* )req.value; + //need to initialized with proper error code + int status = BT_STATUS_SUCCESS; + LOG(INFO) << __func__ << " write descriptor :" << req.handle << + " is resp: " << req.need_rsp << "is prep: " << req.is_prep << " value " << req_value; + + if(req.handle == + ccsControlServiceInfo.call_state_desc) { + LOG(INFO) << __func__ << " call_state_desc descriptor write"; + remoteDevice->call_state_notify = req_value; + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++){ + tCCS_CALL_STATE obj = it->second; + write_desc_value.push_back(obj.index); + write_desc_value.push_back(obj.state); + write_desc_value.push_back(obj.flags); + } + handle = ccsControlServiceInfo.call_state_handle; + } else if(req.handle == + ccsControlServiceInfo.bearer_provider_name_desc) { + remoteDevice->bearer_provider_name_notify = req_value; + LOG(INFO) << __func__ << " bearer_provider_name_desc descriptor write"; + write_desc_value.assign(BearerProviderInfo.name, + BearerProviderInfo.name + strlen((char*)BearerProviderInfo.name)); + handle = ccsControlServiceInfo.bearer_provider_name_handle; + } else if(req.handle == + ccsControlServiceInfo.call_control_point_desc) { + LOG(INFO) << __func__ << " callcontrol_point_desc write"; + write_desc_value.push_back(CallControllerResp.opcode); + write_desc_value.push_back(CallControllerResp.index); + write_desc_value.push_back(CallControllerResp.response_status); + + remoteDevice->call_control_point_notify = req_value; + handle = ccsControlServiceInfo.call_control_point_handle; + } else if(req.handle == + ccsControlServiceInfo.bearer_signal_strength_desc) { + LOG(INFO) << __func__ << " bearer_signal_strength desc write"; + write_desc_value.push_back(BearerProviderInfo.signal); + remoteDevice->bearer_signal_strength_notify = req_value; + handle = ccsControlServiceInfo.bearer_signal_strength_handle; + } else if(req.handle == + ccsControlServiceInfo.bearer_uri_schemes_supported_desc) { + remoteDevice->bearer_uri_schemes_supported_notify = req_value; + write_desc_value.assign(BearerProviderInfo.bearer_schemes_list, + BearerProviderInfo.bearer_schemes_list + strlen((char*)BearerProviderInfo.bearer_schemes_list)); + LOG(INFO) << __func__ << " bearer_schemes_list Desc write"; + } else if(req.handle == + ccsControlServiceInfo.bearer_list_currentcalls_desc) { + std::map::iterator it; + LOG(INFO) << __func__ << " bearer_list_currentcall desc write"; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++){ + tCCS_BEARER_LIST_CURRENT_CALLS obj = it->second; + write_desc_value.push_back(obj.list_length); + write_desc_value.push_back(obj.call_index); + write_desc_value.push_back(obj.call_state); + write_desc_value.push_back(obj.call_flags); + write_desc_value.insert(write_desc_value.end(), + obj.call_uri, obj.call_uri+(obj.list_length-3)); + } + remoteDevice->bearer_current_calls_list_notify = req_value; + handle = ccsControlServiceInfo.bearer_list_currentcalls_handle; + + } else if(req.handle == + ccsControlServiceInfo.bearer_technology_desc) { + LOG(INFO) << __func__ << " bearer_technology_desc write"; + remoteDevice->bearer_technology_changed_notify = req_value; + write_desc_value.push_back(BearerProviderInfo.technology_type); + handle = ccsControlServiceInfo.bearer_technology_handle; + } else if(req.handle == + ccsControlServiceInfo.call_friendly_name_desc) { + LOG(INFO) << __func__ << " call_friendly_name_desc read"; + remoteDevice->call_friendly_name_notify = req_value; + int len = 1 + strlen((char*)FriendlyName.name); + write_desc_value.assign((uint8_t*)&FriendlyName, (uint8_t*)&FriendlyName+len); + handle = ccsControlServiceInfo.call_friendly_name_handle; + } else if(req.handle == + ccsControlServiceInfo.call_termination_reason_desc) { + LOG(INFO) << __func__ << " call_termination_reason_desc write"; + remoteDevice->call_termination_reason_notify = req_value; + handle = ccsControlServiceInfo.call_termination_reason_handle; + write_desc_value.push_back(TerminationReason.index); + write_desc_value.push_back(TerminationReason.reason); + + handle = ccsControlServiceInfo.call_termination_reason_handle; + } else if(req.handle == + ccsControlServiceInfo.call_status_flags_desc) { + LOG(INFO) << __func__ << " Status Flags Desc write"; + remoteDevice->status_flags_notify= req_value; + write_desc_value.push_back(StatusFlags.supported_flags); + handle = ccsControlServiceInfo.call_status_flags_handle; + + } else if(req.handle == + ccsControlServiceInfo.incoming_call_target_bearerURI_desc) { + LOG(INFO) << __func__ << " Incoming target bearer desc write"; + int len = 1 + strlen((char*)IncomingCallTargetUri.incoming_target_uri); + write_desc_value.assign((uint8_t*)&IncomingCallTargetUri, (uint8_t*)&IncomingCallTargetUri+len); + remoteDevice->incoming_call_target_URI_notify= req_value; + handle = ccsControlServiceInfo.incoming_call_target_beareruri_handle; + + } else if(req.handle == + ccsControlServiceInfo.incoming_call_desc) { + LOG(INFO) << __func__ << " Incoming Call desc write"; + remoteDevice->incoming_call_state_notify = req_value; + int len = 1 + strlen((char*)IncomingCallInfo.incoming_uri); + write_desc_value.assign((uint8_t*)&IncomingCallInfo, (uint8_t*)&IncomingCallInfo+len); + + handle = ccsControlServiceInfo.incoming_call_handle; + } else { + LOG(INFO) << __func__ << " descriptor write not matched"; + status = 4; //check error code + } + rsp->oper.WriteDescOp.desc_handle = req.handle; + rsp->oper.WriteDescOp.char_handle = handle; + rsp->oper.WriteDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.WriteDescOp.status = status; + rsp->oper.WriteDescOp.need_rsp = req.need_rsp; + rsp->oper.WriteDescOp.notification = req_value; + rsp->oper.WriteDescOp.value = std::move(write_desc_value); + rsp->event = CCS_DESCRIPTOR_WRITE_RSP; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_EXEC_WRITE_EVT: { + p_data = (tBTA_GATTS*)param; + break; + } + + case BTA_GATTS_CLOSE_EVT: { + //not required + break; + } + + case BTA_GATTS_PHY_UPDATE_EVT: { + + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore phy update " + << p_data->phy_update.status; + status = BT_STATUS_FAIL; + break; + } + rsp->event = CCS_PHY_UPDATE; + rsp->oper.PhyOp.rx_phy = p_data->phy_update.rx_phy; + rsp->oper.PhyOp.tx_phy = p_data->phy_update.tx_phy; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_CONN_UPDATE_EVT: { + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection update device not found"; + break; + } + LOG(INFO) << __func__ << " connection update status " << p_data->phy_update.status; + rsp->event = CCS_CONNECTION_UPDATE; + rsp->oper.ConnectionUpdateOp.remoteDevice = remoteDevice; + rsp->oper.ConnectionUpdateOp.remoteDevice->latency = p_data->conn_update.latency; + rsp->oper.ConnectionUpdateOp.remoteDevice->timeout = p_data->conn_update.timeout; + rsp->oper.ConnectionUpdateOp.remoteDevice->interval = p_data->conn_update.interval; + rsp->oper.ConnectionUpdateOp.status = p_data->conn_update.status; + rsp->remoteDevice = remoteDevice; + break; + } + + case CCS_ACTIVE_DEVICE_UPDATE: + { + tCCS_SET_ACTIVE_DEVICE *data = (tCCS_SET_ACTIVE_DEVICE *)param; + LOG(INFO) << __func__ << " CCS_ACTIVE_DEVICE_UPDATE address " << data->address; + if (cc_instance->remoteDevices.FindByAddress(data->address) != NULL) { + cc_instance->remoteDevices.AddSetActiveDevice(data); + } else { + LOG(ERROR) << __func__ << " CCS_ACTIVE_DEVICE_UPDATE failed as given remote is not registered for notif " << data->address; + } + break; + } + case CCS_CONNECTION_CLOSE_EVENT: + { + break; + } + case CCS_CALL_STATE_UPDATE: + { + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++){ + tCCS_CALL_STATE obj = it->second; + _data.push_back(obj.index); + _data.push_back(obj.state); + _data.push_back(obj.flags); + } + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_state_handle; + isCallControllerOpUsed = true; + break; + } + case CCS_BEARER_NAME_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_NAME_UPDATE"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO*) param; + uint16_t len = strlen((char*)data->name); + ReverseByteOrder(data->name, len); + memcpy(BearerProviderInfo.name, data->name, len); + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_provider_name_handle; + _data.assign(BearerProviderInfo.name, + BearerProviderInfo.name + len); + isCallControllerOpUsed = true; + break; + } + + case CCS_OPT_OPCODES: + { + LOG(INFO) << __func__ << " CCS_OPT_OPCODES"; + tCCS_SUPP_OPTIONAL_OPCODES *data = (tCCS_SUPP_OPTIONAL_OPCODES*) param; + SupportedOptionalOpcodes.supp_opcode = data->supp_opcode; + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_control_point_opcode_supported_handle; + + _data.push_back(SupportedOptionalOpcodes.supp_opcode); + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_CURRENT_CALL_LIST_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_CURRENT_CALL_LIST_UPDATE"; + std::map::iterator it; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++){ + tCCS_BEARER_LIST_CURRENT_CALLS obj = it->second; + _data.push_back(obj.list_length); + _data.push_back(obj.call_index); + _data.push_back(obj.call_state); + _data.push_back(obj.call_flags); + _data.insert(_data.end(), + obj.call_uri, obj.call_uri+(obj.list_length-3)); + } + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_list_currentcalls_handle; + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_SIGNAL_STRENGTH_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_SIGNAL_STRENGTH_UPDATE"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO *) param; + BearerProviderInfo.signal = data->signal; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_signal_strength_handle; + //control whether notification has to happen based + //on reporting Interval time or not + rsp->force = false; + _data.push_back(BearerProviderInfo.signal); + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_TECHNOLOGY_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_TECHNOLOGY_UPDATE"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO *) param; + BearerProviderInfo.technology_type = data->technology_type; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_technology_handle; + _data.push_back(BearerProviderInfo.technology_type); + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_URI_SCHEMES_SUPPORTED: { + LOG(INFO) << __func__ << " CCS_BEARER_URI_SCHEMES_SUPPORTED"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO*) param; + BearerProviderInfo.bearer_list_len = data->bearer_list_len; + LOG(INFO) << __func__ << " CCS_BEARER_URI_SCHEMES_SUPPORTED: len " <bearer_schemes_list, BearerProviderInfo.bearer_list_len); + memcpy(BearerProviderInfo.bearer_schemes_list, data->bearer_schemes_list, BearerProviderInfo.bearer_list_len); + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_uri_schemes_supported_handle; + isCallControllerOpUsed = true; + _data.assign(BearerProviderInfo.bearer_schemes_list, + BearerProviderInfo.bearer_schemes_list + BearerProviderInfo.bearer_list_len); + isCallControllerOpUsed = true; + break; + } + + case CCS_INCOMING_CALL_UPDATE: + { + LOG(INFO) << __func__ << " CCS_INCOMING_CALL_UPDATE"; + tCCS_INCOMING_CALL* data = (tCCS_INCOMING_CALL *) param; + IncomingCallInfo.index = data->index; + uint16_t len = strlen((char*)data->incoming_uri); + ReverseByteOrder(data->incoming_uri, len); + memcpy(IncomingCallInfo.incoming_uri, data->incoming_uri, len); + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.incoming_call_handle; + _data.push_back(IncomingCallInfo.index); + _data.insert(_data.end(), IncomingCallInfo.incoming_uri, + IncomingCallInfo.incoming_uri + len); + isCallControllerOpUsed = true; + break; + } + + case CCS_INCOMING_TARGET_URI_UPDATE: + { + LOG(INFO) << __func__ << " CCS_INCOMING_TARGET_URI_UPDATE"; + tCCS_INCOMING_CALL_URI* data = (tCCS_INCOMING_CALL_URI *) param; + IncomingCallTargetUri.index = data->index; + uint16_t len = strlen((char*)data->incoming_target_uri); + LOG(INFO) << __func__ << " CCS_INCOMING_TARGET_URI_UPDATE: urilen " << len; + ReverseByteOrder(data->incoming_target_uri, len); + memcpy(IncomingCallTargetUri.incoming_target_uri, data->incoming_target_uri, len); + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.incoming_call_target_beareruri_handle; + _data.push_back(IncomingCallTargetUri.index); + _data.insert(_data.end(), IncomingCallTargetUri.incoming_target_uri, + IncomingCallTargetUri.incoming_target_uri + len); + isCallControllerOpUsed = true; + break; + } + + case CCS_TERMINATION_REASON_UPDATE: + { + LOG(INFO) << __func__ << " CCS_TERMINATION_REASON_UPDATE"; + tCCS_TERM_REASON* data = (tCCS_TERM_REASON *) param; + TerminationReason.index = data->index; + TerminationReason.reason = data->reason; + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_termination_reason_handle; + _data.push_back(TerminationReason.index); + _data.push_back(TerminationReason.reason); + + isCallControllerOpUsed = true; + break; + } + + case CCS_STATUS_FLAGS_UPDATE: + { + LOG(INFO) << __func__ << " CCS_STATUS_FLAGS_UPDATE"; + tCCS_STATUS_FLAGS *data = (tCCS_STATUS_FLAGS*) param; + + ReverseByteOrder((unsigned char*)&data->supported_flags, sizeof(data->supported_flags)); + StatusFlags.supported_flags = data->supported_flags; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_status_flags_handle; + _data.push_back(StatusFlags.supported_flags); + isCallControllerOpUsed = true; + break; + } + + case CCS_CCID_UPDATE: + { + LOG(INFO) << __func__ << " CCS_CCID_UPDATE"; + tCCS_CONTENT_CONTROL_ID *data = (tCCS_CONTENT_CONTROL_ID *) param; + ReverseByteOrder((unsigned char*)&data->ccid, sizeof(data->ccid)); + CcidInfo.ccid = (uint32_t)data->ccid; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.ccid_handle; + _data.push_back(CcidInfo.ccid); + isCallControllerOpUsed = true; + break; + } + + case CCS_CALL_CONTROL_RESPONSE: + { + LOG(INFO) << __func__ << " CCS_CALL_CONTROL_RESPONSE"; + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_control_point_handle; + _data.push_back(CallControllerResp.opcode); + _data.push_back(CallControllerResp.index); + _data.push_back(CallControllerResp.response_status); + isCallControllerOpUsed = true; + break; + } + default: + LOG(INFO) << __func__ << " event not matched !!"; + break; + } + + if(rsp->event != CCS_NONE_EVENT) { + if (isCallControllerOpUsed == true) { + rsp->oper.CallControllerOp.data = std::move(_data); + LOG(INFO) << __func__ << " After Moving size " << rsp->oper.CallControllerOp.data.size(); + } + CCSHandler(rsp->event, rsp); + } + if(rsp) { + LOG(INFO) << __func__ << "free rsp data"; + free(rsp); + } +} + + + +void BTCcCback(tBTA_GATTS_EVT event, tBTA_GATTS* param) { + + HandleCcsEvent((uint32_t)event, param); +} + + + bool DeviceStateConnectionHandler(uint32_t event, void* param) { + LOG(INFO) << __func__ << " device connected handle " << event; + tcc_resp_t *p_data = (tcc_resp_t *) param; + switch (event) { + + case CCS_NOTIFY_ALL: { + LOG(INFO) << __func__ << " device notify all"; + if (p_data->handle == ccsControlServiceInfo.bearer_signal_strength_handle) { + if (p_data->force == false && + p_data->remoteDevice->signal_strength_report_interval != 0) { + LOG(INFO) << __func__ << "Not a timer expired push, dont notify to remote"; + break; + } + } + + GattsOpsQueue::SendNotification(p_data->remoteDevice->conn_id, p_data->handle, + p_data->oper.CallControllerOp.data, false); + break; + } + + case CCS_READ_RSP: + + LOG(INFO) << __func__ << " device CCS_READ_RSP update " << event; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + break; + + case CCS_DESCRIPTOR_READ_RSP: + + LOG(INFO) << __func__ << " device CCS_DESCRIPTOR_READ_RSP update " << event; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadDescOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + break; + + case CCS_DESCRIPTOR_WRITE_RSP: + { + LOG(INFO) << __func__ << " device CCS_DESCRIPTOR_WRITE_RSP update rsp: " << p_data->oper.WriteDescOp.need_rsp; + tGATTS_RSP rsp_struct; + rsp_struct.attr_value.handle = p_data->rsp_value.attr_value.handle; + rsp_struct.attr_value.offset = p_data->rsp_value.attr_value.offset; + if (p_data->remoteDevice->congested == false && + p_data->oper.WriteDescOp.need_rsp) { + //send rsp to write + LOG(INFO) << __func__ << " gett send rsp"; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.WriteDescOp.trans_id, + p_data->oper.WriteDescOp.status, &rsp_struct); + } + if (!p_data->oper.WriteDescOp.status && p_data->oper.WriteDescOp.notification) { + //notify update to register device + GattsOpsQueue::SendNotification(p_data->remoteDevice->conn_id, + p_data->oper.WriteDescOp.char_handle, + p_data->oper.WriteDescOp.value, false); + } else { + LOG(INFO) << __func__ << " notification disable handle : " << p_data->oper.WriteDescOp.char_handle; + } + break; + } + + case CCS_WRITE_RSP: { + LOG(INFO) << __func__ << " device CCS_WRITE_RSP update " << event; + bool need_rsp = p_data->oper.WriteOp.need_rsp; + LOG(INFO) << __func__ << " device CCS_WRITE_RSP update " << event << " need_rsp: " <remoteDevice->conn_id, p_data->oper.WriteOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + } + break; + } + + case CCS_CONNECTION_UPDATE: { + LOG(INFO) << __func__ << " device cconnection update " << event; + break; + } + + case CCS_PHY_UPDATE: { + LOG(INFO) << __func__ << " device CCS_PHY_UPDATE update " << event; + p_data->remoteDevice->rx_phy = p_data->oper.PhyOp.rx_phy; + p_data->remoteDevice->tx_phy = p_data->oper.PhyOp.tx_phy; + break; + } + + case CCS_MTU_UPDATE: { + LOG(INFO) << __func__ << " device CCS_MTU_UPDATE update " << event; + p_data->remoteDevice->mtu = p_data->oper.MtuOp.mtu; + break; + } + + case CCS_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device CCS_CONGESTION_UPDATE update: " << event; + CcpCongestionUpdate(p_data); + break; + } + + case CCS_DISCONNECTION: { + LOG(INFO) << __func__ << " device CCS_DISCONNECTION remove " << event; + cc_instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + cc_instance->ConnectionStateCallback(CCS_DISCONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case CCS_CONNECTION_CLOSE_EVENT: { + LOG(INFO) << __func__ << " device connection closing"; + // Close active connection + if (p_data->remoteDevice->conn_id != 0) + BTA_GATTS_Close(p_data->remoteDevice->conn_id); + else + BTA_GATTS_CancelOpen(ccsControlServiceInfo.server_if, p_data->remoteDevice->peer_bda, true); + // Cancel pending background connections + BTA_GATTS_CancelOpen(ccsControlServiceInfo.server_if, p_data->remoteDevice->peer_bda, false); + break; + } + + case CCS_BOND_STATE_CHANGE_EVENT: + LOG(INFO) << __func__ << " Bond state change"; + cc_instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + break; + + default: + LOG(INFO) << __func__ << " event not matched"; + break; + } + + return BT_STATUS_SUCCESS; +} + + +bool DeviceStateDisconnectedHandler(uint32_t event, void* param) { + LOG(INFO) << __func__ << " device disconnected handle " << event; + tcc_resp_t *p_data = (tcc_resp_t *) param; + switch (event) { + case CCS_CONNECTION: + { + LOG(INFO) << __func__ << " connection processing " << event; + p_data->remoteDevice->state = CCS_CONNECTED; + p_data->remoteDevice->call_state_notify = 0x00; + p_data->remoteDevice->bearer_provider_name_notify = 0x00; + p_data->remoteDevice->call_control_point_notify = 0x00; + p_data->remoteDevice->bearer_technology_changed_notify = 0x00; + p_data->remoteDevice->bearer_uci_notify = 0x00; + p_data->remoteDevice->bearer_current_calls_list_notify = 0x00; + p_data->remoteDevice->call_friendly_name_notify = 0x00; + p_data->remoteDevice->call_termination_reason_notify = 0x00; + p_data->remoteDevice->congested = false; + // p_data->remoteDevice->conn_id; + + p_data->remoteDevice->timeout = 0; + p_data->remoteDevice->latency = 0; + p_data->remoteDevice->interval = 0; + p_data->remoteDevice->rx_phy = 0; + p_data->remoteDevice->tx_phy = 0; + cc_instance->ConnectionStateCallback(CCS_CONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case CCS_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device CCS_CONGESTION_UPDATE update: " << event; + CcpCongestionUpdate(p_data); + break; + } + + case CCS_MTU_UPDATE: + case CCS_PHY_UPDATE: + case CCS_DISCONNECTED: + case CCS_READ_RSP: + case CCS_DESCRIPTOR_READ_RSP: + case CCS_WRITE_RSP: + case CCS_DESCRIPTOR_WRITE_RSP: + case CCS_NOTIFY_ALL: + case CCS_DISCONNECTION: + + default: + //ignore event + LOG(INFO) << __func__ << " Ignore event " << event; + break; + } + return BT_STATUS_SUCCESS; +} + +bool is_digits(const std::string &str) +{ + return str.find_first_not_of("0123456789+") == std::string::npos; +} + +bool IsValidOriginateUri(std::string uri) { + bool ret = false; + std::vector out; + char *token; + char* rest = const_cast(uri.c_str()); + while ((token = strtok_r(rest, ":", &rest))) + { + out.push_back(std::string(token)); + } + if (out.size() == 2) { + if (out[0].compare("tel") == 0 && is_digits(out[1])) { + ret = true; + } + } + LOG(INFO) << __func__ << " ret: " << ret; + return ret; +} + +bool CCSActiveProfile(RawAddress addr) { + bool isCCSActiveProfile = false; + int32_t activeProfile; + activeProfile = osi_property_get_int32("persist.vendor.qcom.bluetooth.default_profiles",0); + if ((activeProfile&0x2000) == 0x2000) { + isCCSActiveProfile = true; + } + LOG(INFO) << __func__ << " activeProfile "<< activeProfile <<" for" << addr; + return isCCSActiveProfile; +} + +bool CCSHandler(uint32_t event, void* param) { + + tcc_resp_t *p_data = (tcc_resp_t *)param; + + CallControllerDeviceList *device = p_data->remoteDevice; + LOG(INFO) << __func__ << " inactive ccs handle event "<< bta_cc_event_str(event); + switch(p_data->event) { + case CCS_NOTIFY_ALL: { + + std::vectornotifyDevices = cc_instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + break; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + if (CCSActiveProfile(it->peer_bda)) { + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + p_data->remoteDevice = cc_instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data); + } + } + break; + } + case CCS_WRITE_RSP: { + + LOG(INFO) << __func__ << " Push Write response first: " << device->state; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data); + //Process the values now + uint8_t* data = p_data->oper.WriteOp.data; + int len = p_data->oper.WriteOp.len; + RawAddress peer_bda = p_data->remoteDevice->peer_bda; + std::map::iterator it; + bool valid_index = false; + uint8_t indices[10]; + char URI[MAX_URI_SIZE]; + int i; + uint32_t handle = p_data->oper.WriteOp.char_handle; + if(handle == ccsControlServiceInfo.call_control_point_handle) { + CallControllerOps.operation = data[0]; + LOG(INFO) << __func__ << " operation: " << std::hex << CallControllerOps.operation; + if (gIsActiveCC == true && !cc_instance->remoteDevices.FindActiveDevice(p_data->remoteDevice) && + (CallControllerOps.operation == CALL_TERMINATE || + CallControllerOps.operation == CALL_LOCAL_HOLD || + CallControllerOps.operation == CALL_LOCAL_RETRIEVE)) { + LOG(ERROR) << __func__ << "TERM|HOLD|RET|JOIN triggered from non-active Device"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_OPCODE_UNSUCCESSFUL, peer_bda); + return true; + } + switch(CallControllerOps.operation) { + case CALL_ACCEPT:{ + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0] = data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++){ + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0] && obj.state == CCS_STATE_INCOMING) { + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + cc_instance->CallControlResponse(CallControllerOps.operation, + indices[0], + CCS_STATUS_SUCCESS, peer_bda); + } else { + LOG(INFO) << " ACCEPT ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_TERMINATE: { + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0] = data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++) { + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0]) { + LOG(INFO) << __func__ << " call index match: " << indices[0]; + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + cc_instance->CallControlResponse(CallControllerOps.operation, + indices[0], + CCS_STATUS_SUCCESS, peer_bda); + gIsTerminatedInitiatedFromClient = true; + gTerminateIntiatedIndex = indices[0]; + } else { + LOG(INFO) << " TERMINATE ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_LOCAL_HOLD: { + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0]= data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++) { + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0] && + (obj.state == CCS_STATE_INCOMING || obj.state == CCS_STATE_ACTIVE)) { + LOG(INFO) << __func__ << " call index match: " << indices[0]; + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + + } else { + LOG(INFO) << " CALL_LOCAL_HOLD ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_LOCAL_RETRIEVE: { + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0]= data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++) { + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0] && (obj.state == CCS_STATE_LOCAL_HELD )) { + LOG(INFO) << __func__ << " call index match: " << indices[0]; + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange( CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + + } else { + LOG(INFO) << " CALL_LOCAL_RETRIEVE ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_ORIGINATE: { + LOG(INFO) << __func__ << " CALL_ORIGINATE match:"; + memset(URI, '\0', sizeof(char)*MAX_URI_SIZE); + strlcpy(URI, (char*)&data[1], len); + LOG(INFO) << __func__ << " ORIGINATE: " << URI; + if (IsValidOriginateUri(URI)) { + cc_instance->CallControlPointChange(CallControllerOps.operation, /*unused*/0, + /*count*/0, URI, device->peer_bda); + } else { + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_OUTGOING_URI, peer_bda); + } + break; + } + case CALL_JOIN: { + int count = len-1; + uint8_t *start_of_indicies = &(data[1]); + uint8_t indices[MAX_CCS_CONNECTION]; + valid_index = true; + bool atleastOneHeldCall = false; + for (int i=0; i::iterator j = CallStatelist.find(indices[i]); + if (j == CallStatelist.end()) { + valid_index = false; + break; + } else if (j->second.state == CCS_STATE_LOCAL_HELD) { + atleastOneHeldCall = true; + } + } + if (CallStatelist.size() < 2 || atleastOneHeldCall == false) { + LOG(INFO) << __func__ << " Join is not valid: "; + valid_index = false; + } + if (count > 1 && valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, + indices, count, + NULL, device->peer_bda); + } else { + LOG(INFO) << __func__ << " no valid indices found for JOIN operation "; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + default: + LOG(ERROR) << __func__ << " Unhandled Event: " << CallControllerOps.operation; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_OPCODE_NOT_SUPPORTED, peer_bda); + } + } else if (handle == ccsControlServiceInfo.bearer_signal_strength_report_interval_handle) { + if (len != 1) { + LOG(ERROR) << " Invalid input for SSR interval"; + return BT_STATUS_SUCCESS; + } + LOG(INFO) << __func__ << " signal strength repo Interval: " << data[0]; + BearerProviderInfo.signal_report_interval = data[0]; + cc_instance->remoteDevices.UpdateSSReportingInterval(device->peer_bda, BearerProviderInfo.signal_report_interval); + } + break; + } + + case CCS_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device CCS_CONGESTION_UPDATE update: " << event; + CcpCongestionUpdate(p_data); + break; + } + + case CCS_READ_RSP: + case CCS_DESCRIPTOR_READ_RSP: + case CCS_DESCRIPTOR_WRITE_RSP: + case CCS_CONNECTION_UPDATE: + case CCS_PHY_UPDATE: + case CCS_CONNECTION: + LOG(INFO) << __func__ << " calling device state " << device->state; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data); + break; + + default: + LOG(INFO) << __func__ << " event is not in list"; + break; + } + + return BT_STATUS_SUCCESS; + +} + +void CcpCongestionUpdate(tcc_resp_t* p_data) { + p_data->remoteDevice->congested = p_data->oper.CongestionOp.congested; + LOG(INFO) << __func__ << ": conn_id: " << p_data->remoteDevice->conn_id + << ", congested: " << p_data->remoteDevice->congested; + + GattsOpsQueue::CongestionCallback(p_data->remoteDevice->conn_id, + p_data->remoteDevice->congested); +} diff --git a/le_audio/system/bt/bta/csip/bta_csip_act.cc b/le_audio/system/bt/bta/csip/bta_csip_act.cc new file mode 100644 index 0000000000000000000000000000000000000000..ff83083b788c2682a34c36f6aa1f73d6fa55d671 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_act.cc @@ -0,0 +1,1686 @@ +/****************************************************************************** + +Copyright (c) 2020, The Linux Foundation. All rights reserved. +* +*****************************************************************************/ + +/****************************************************************************** +* + +* Copyright 2009-2013 Broadcom Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at: +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +******************************************************************************/ + +/****************************************************************************** + * + * This file contains the CSIP Client action functions. + * + ******************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include "bta_csip_int.h" +#include "bta_csip_api.h" +#include "bta_gatt_api.h" +#include "bta_gatt_queue.h" +#include "btm_api.h" +#include "btm_ble_api.h" +#include "btm_int.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "bta_dm_api.h" +#include "bta_dm_adv_audio.h" + +/* CSIS Service UUID */ +Uuid CSIS_SERVICE_UUID = Uuid::FromString("1846"); +/* CSIS Characteristic UUID's */ +Uuid CSIS_SERVICE_SIRK_UUID = Uuid::FromString("2B84"); +Uuid CSIS_SERVICE_SIZE_UUID = Uuid::FromString("2B85"); +Uuid CSIS_SERVICE_LOCK_UUID = Uuid::FromString("2B86"); +Uuid CSIS_SERVICE_RANK_UUID = Uuid::FromString("2B87"); + +/******************************************************************************* + * + * Function bta_csip_api_enable + * + * Description This function completes tasks to be done on BT ON + * + * Parameters: p_cback - callbacks from btif layer + * + ******************************************************************************/ +void bta_csip_api_enable(tBTA_CSIP_CBACK *p_cback) { + APPL_TRACE_DEBUG("%s", __func__); + + bta_csip_cb = tBTA_CSIP_CB(); + bta_csip_cb.p_cback = p_cback; + + // register with GATT CLient interface + bta_csip_gattc_register(); + + bta_csip_load_coordinated_sets_from_storage(); +} + +/******************************************************************************* + * + * Function bta_csip_api_disable + * + * Description This function completes tasks to be done on BT OFF + * + * Parameters: None + * + ******************************************************************************/ +void bta_csip_api_disable() { + std::vector &dev_cb = bta_csip_cb.dev_cb; + + /* close all active GATT Connections */ + for (tBTA_CSIP_DEV_CB& p_cb: dev_cb) { + if (p_cb.state == BTA_CSIP_CONN_ST) { + BTA_GATTC_Close(p_cb.conn_id); + } + } + + /* Deregister GATT Interface */ + BTA_GATTC_AppDeregister(bta_csip_cb.gatt_if); +} + +/******************************************************************************* + * + * Function bta_csip_app_register + * + * Description API used to register App/Module for CSIP callbacks. + * operation + * + * Parameters: app_uuid - Application UUID. + * p_cback - Application callbacks. + * cb - callback after registration. + ******************************************************************************/ +void bta_csip_app_register (const Uuid& app_uuid, tBTA_CSIP_CBACK* p_cback, + BtaCsipAppRegisteredCb cb) { + uint8_t i; + tBTA_CSIP_STATUS status = BTA_CSIP_FAILURE; + + for (i = 0; i < BTA_CSIP_MAX_SUPPORTED_APPS; i++) { + if (!bta_csip_cb.app_rcb[i].in_use) { + bta_csip_cb.app_rcb[i].in_use = true; + bta_csip_cb.app_rcb[i].app_id = i; + bta_csip_cb.app_rcb[i].p_cback = p_cback; + status = BTA_CSIP_SUCCESS; + break; + } + } + + if (status == BTA_CSIP_SUCCESS) { + LOG(INFO) << "CSIP App Registered Succesfully. App ID: " << +i; + } else { + LOG(ERROR) << "CSIP App Registration failed. App Limit reached"; + } + + // Give callback to registering App/Module + if (!cb.is_null()) cb.Run(status, i); +} + +/******************************************************************************* + * + * Function bta_csip_app_unregister + * + * Description API used to unregister App/Module for CSIP callbacks. + * + * Parameters: app_id: ID of the application to be unregistered. + * + ******************************************************************************/ +void bta_csip_app_unregister(uint8_t app_id) { + if (app_id >= BTA_CSIP_MAX_SUPPORTED_APPS) { + LOG(ERROR) << __func__ << " Invalid App ID: " << +app_id; + return; + } + + bta_csip_cb.app_rcb[app_id].in_use = false; + bta_csip_cb.app_rcb[app_id].p_cback = NULL; +} + +/******************************************************************************* + * + * Function bta_csip_gattc_callback + * + * Description This is GATT client callback function used in BTA CSIP. + * + * Parameters: event - received from GATT + * p_data - data associated with the event + * + ******************************************************************************/ +static void bta_csip_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + tBTA_CSIP_DEV_CB* p_dev_cb; + + APPL_TRACE_DEBUG("bta_csip_gattc_callback event = %d", event); + + if (p_data == NULL) return; + + switch (event) { + case BTA_GATTC_OPEN_EVT: + p_dev_cb = bta_csip_find_dev_cb_by_bda(p_data->open.remote_bda); + if (p_dev_cb) { + bta_csip_sm_execute(p_dev_cb, BTA_CSIP_GATT_OPEN_EVT, + (tBTA_CSIP_REQ_DATA*)&p_data->open); + } + break; + + case BTA_GATTC_CLOSE_EVT: + p_dev_cb = bta_csip_find_dev_cb_by_bda(p_data->close.remote_bda); + if (p_dev_cb) { + APPL_TRACE_DEBUG("BTA_GATTC_CLOSE_EVT state = %d", p_dev_cb->state); + bta_csip_sm_execute(p_dev_cb, BTA_CSIP_GATT_CLOSE_EVT, + (tBTA_CSIP_REQ_DATA*)&p_data->close); + } + break; + + case BTA_GATTC_SEARCH_CMPL_EVT: { + tBTA_GATTC_SEARCH_CMPL* p_srch_data = &p_data->search_cmpl; + tBTA_CSIP_DISC_SET disc_params = {.conn_id = p_srch_data->conn_id, + .status = p_srch_data->status + }; + p_dev_cb = bta_csip_get_dev_cb_by_cid(p_srch_data->conn_id); + if (p_dev_cb) { + disc_params.addr = p_dev_cb->addr; + p_dev_cb->is_disc_external = true; + } + bta_csip_gatt_disc_cmpl_act(&disc_params); + bta_csip_sm_execute(p_dev_cb, BTA_CSIP_OPEN_CMPL_EVT, NULL); + } + break; + + case BTA_GATTC_NOTIF_EVT: { + bta_csip_handle_notification(&p_data->notify); + } + break; + + default: + break; + } +} + +/******************************************************************************* + * + * Function bta_csip_gattc_register + * + * Description API used to register GATT interface for CSIP operations. + * + * Parameters: None + * + ******************************************************************************/ +void bta_csip_gattc_register() { + APPL_TRACE_DEBUG("%s", __func__); + + BTA_GATTC_AppRegister(bta_csip_gattc_callback, + base::Bind([](uint8_t client_id, uint8_t status) { + tBTA_CSIP_STATUS csip_status = BTA_CSIP_FAILURE; + if (status == GATT_SUCCESS) { + bta_csip_cb.gatt_if = client_id; + csip_status = BTA_CSIP_SUCCESS; + } else { + bta_csip_cb.gatt_if = BTA_GATTS_INVALID_IF; + } + + /* BTA_GATTC_AppRegister is done */ + if (bta_csip_cb.p_cback) { + LOG(INFO) << "CSIP GATT IF : " + << +bta_csip_cb.gatt_if; + } + }), true); +} + +/******************************************************************************* + * + * Function bta_csip_process_set_lock_act + * + * Description This function processes lock/unlock request. + * + * Parameters: lock_param: params used in LOCK/UNLOCK request. + * + ******************************************************************************/ +void bta_csip_process_set_lock_act(tBTA_SET_LOCK_PARAMS lock_param) { + LOG(INFO) << __func__ << ": App ID = " << +lock_param.app_id + << ", Set ID = " << +lock_param.set_id + << ", Value = " << +lock_param.lock_value; + + tBTA_CSET_CB* cset_cb = bta_csip_get_cset_cb_by_id (lock_param.set_id); + if (!cset_cb || !bta_csip_is_valid_lock_request(&lock_param)) { + tBTA_LOCK_STATUS_CHANGED res = {.app_id = lock_param.app_id, + .set_id = lock_param.set_id, + .status = INVALID_REQUEST_PARAMS}; + bta_csip_send_lock_req_cmpl_cb(res); + return; + } + + // Add request in the queue if one is already in progress for this set + if (cset_cb->request_in_progress) { + cset_cb->lock_req_queue.push(lock_param); + LOG(INFO) << __func__ << " pending lock requests in queue for Set:" + << +lock_param.set_id + << " Pending Requests = " << +(int)cset_cb->lock_req_queue.size(); + return; + } + + bta_csip_form_lock_request(lock_param, cset_cb); +} + +/******************************************************************************* + * + * Function bta_csip_process_set_lock_act + * + * Description This function forms request (LOCK/UNLOCK and order of the + * set members). + * + * Parameters: lock_param: params used in LOCK/UNLOCK request. + * cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_form_lock_request(tBTA_SET_LOCK_PARAMS lock_param, + tBTA_CSET_CB* cset_cb) { + cset_cb->request_in_progress = true; + + std::vector ordered_members; + + if (lock_param.lock_value == LOCK_VALUE) { + ordered_members = bta_csip_arrange_set_members_by_order(cset_cb->set_id, + lock_param.members_addr, true); + } else { + ordered_members = bta_csip_arrange_set_members_by_order(cset_cb->set_id, + lock_param.members_addr, false); + } + + //debug log + for (int i = 0; i < (int)ordered_members.size(); i++) { + APPL_TRACE_DEBUG("%s: Member %d = %s", __func__, (i+1), + ordered_members[i].ToString().c_str()); + } + + // update current request in CB + cset_cb->cur_lock_req = {lock_param.app_id, lock_param.set_id, lock_param.lock_value, + 0, ordered_members}; + + // update current response in CB + cset_cb->cur_lock_res = {}; + cset_cb->cur_lock_res.app_id = lock_param.app_id; + cset_cb->cur_lock_res.set_id = lock_param.set_id; + cset_cb->cur_lock_res.value = UNLOCK_VALUE; + + /* LOCK Request */ + if (cset_cb->cur_lock_req.value == LOCK_VALUE) { + cset_cb->cur_lock_res.status = ALL_LOCKS_ACQUIRED; + /* check if lock request for this set was denied earlier */ + if (bta_csip_validate_req_for_denied_sm(cset_cb)) { + bta_csip_get_next_lock_request(cset_cb); + + /* proceed with request otherwise*/ + } else { + bta_csip_send_lock_req_act(cset_cb); + } + /* UNLOCK Request*/ + } else { + bta_csip_send_unlock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_validate_req_for_denied_sm + * + * Description This function validates request if received for denied set + * member + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +bool bta_csip_validate_req_for_denied_sm (tBTA_CSET_CB* cset_cb) { + bool is_denied = false; + tBTA_LOCK_REQUEST& lock_req = cset_cb->cur_lock_req; + + for (RawAddress& addr: lock_req.members_addr) { + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(addr); + if (!p_cb) { + APPL_TRACE_ERROR("%s: Device CB not found for %s", __func__, + addr.ToString().c_str()); + continue; + } + + tBTA_CSIS_SRVC_INFO* srvc = bta_csip_get_csis_instance(p_cb, lock_req.set_id); + if (!srvc) { + APPL_TRACE_ERROR("%s: CSIS instance not found for %s", __func__, + addr.ToString().c_str()); + continue; + } + + if (!srvc->denied_applist.empty()) { + is_denied = true; + // add this app_id in the denied_app_list + srvc->denied_applist.push_back(lock_req.app_id); + cset_cb->cur_lock_res.status = LOCK_DENIED; + cset_cb->cur_lock_res.addr.push_back(addr); + } + } + + if (is_denied) { + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + } + + return is_denied; +} + +/******************************************************************************* + * + * Function bta_csip_lock_release_by_denial_cb + * + * Description This callback function is called when set member is unlocked + * after unlock request is sent post lock denial. + * + * Parameters: GATT operation callback params. + * + ******************************************************************************/ +void bta_csip_lock_release_by_denial_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)data; + tBTA_CSIP_DEV_CB* dev_cb = cset_cb->cur_dev_cb; + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + LOG(INFO) << __func__ << " Released lock for device: " << dev_cb->addr + << ", Set ID: " << +cset_cb->set_id; + + if (srvc && status == GATT_SUCCESS) { + srvc->lock = UNLOCK_VALUE; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), srvc->lock_applist.end(), + cset_cb->cur_lock_req.app_id), srvc->lock_applist.end()); + } + + // release next set member with lower rank + bta_csip_handle_lock_denial(cset_cb); +} + +/******************************************************************************* + * + * Function bta_csip_handle_lock_denial + * + * Description This function is called when lock has been denied by one of + * the set members. + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_handle_lock_denial(tBTA_CSET_CB* cset_cb) { + // start lock release procedure for acquired locks + int8_t cur_idx = cset_cb->cur_lock_req.cur_idx - 1; + cset_cb->cur_lock_req.cur_idx--; + + if (cur_idx >= 0) { + RawAddress bd_addr = cset_cb->cur_lock_req.members_addr[cur_idx]; + cset_cb->cur_dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + // check if locked by other app + if (!cset_cb->cur_dev_cb || !srvc || + bta_csip_is_locked_by_other_apps(srvc, cset_cb->cur_lock_req.app_id)) { + LOG(INFO) << "Invalid device or service CB or" + << " other apps have locked this set member(" << bd_addr << "). Skip."; + bta_csip_handle_lock_denial(cset_cb); + return; + } + + // Lock value in vector format (one uint8_t size element with ) + std::vector unlock_value(1, UNLOCK_VALUE); + BtaGattQueue::WriteCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, unlock_value, GATT_WRITE, bta_csip_lock_release_by_denial_cb, cset_cb); + + } else { + bta_csip_get_next_lock_request(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_lock_req_cb + * + * Description This callback function is called when set member is locked + * by remote device after LOCK Request + * + * Parameters: GATT operation callback params. + * + ******************************************************************************/ +void bta_csip_lock_req_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + void* data) { + LOG(INFO) << __func__ << " status = " << +status; + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)data; + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_get_dev_cb_by_cid(conn_id); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(dev_cb, cset_cb->set_id); + + /* Device control block or corresponding CSIS service instance not found */ + if (!dev_cb || !srvc) { + APPL_TRACE_ERROR("%s: Device CB not found for conn_id = %d", __func__, conn_id); + cset_cb->cur_lock_req.cur_idx++; + alarm_cancel(cset_cb->unresp_timer); + bta_csip_send_lock_req_act(cset_cb); + return; + } + + /*check if this response is received from unresponsive set member */ + if (dev_cb->unresponsive) { + LOG(INFO) << __func__ << " unresponsive remote: " << dev_cb->addr; + bta_csip_handle_unresponsive_sm_res(srvc, status); + dev_cb->unresponsive = false; + return; + } + // cancel alarm (used for unresponsive set member) + alarm_cancel(cset_cb->unresp_timer); + + if (status == CSIP_LOCK_DENIED) { + LOG(INFO) << __func__ << " Locked Denied by " << dev_cb->addr; + srvc->lock = UNLOCK_VALUE; + cset_cb->cur_lock_res.value = UNLOCK_VALUE; + cset_cb->cur_lock_res.status = LOCK_DENIED; + + // add member to the response list for which lock is denied and clear others + cset_cb->cur_lock_res.addr.clear(); + cset_cb->cur_lock_res.addr.push_back(dev_cb->addr); + + // add app_id in the denied applist + srvc->denied_applist.push_back(cset_cb->cur_lock_req.app_id); + // Give callback to upper layer that lock is denied + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + // release all the acquired locks till now + bta_csip_handle_lock_denial(cset_cb); + + /* PTS: Remote responding with invalid value */ + } else if (status == CSIP_INVALID_LOCK_VALUE) { + LOG(ERROR) << __func__ << " remote " << dev_cb->addr + << " responded with INVALID Value"; + /* for PTS to ensure set coordinator is working fine */ + BtaGattQueue::ReadCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, NULL, NULL); + + /* Stop locking remaining set members and inform requesting app */ + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + /* Process next lock request from pending queue */ + bta_csip_get_next_lock_request(cset_cb); + } else { + if (status == GATT_SUCCESS || status == CSIP_LOCK_ALREADY_GRANTED) { + LOG(INFO) << __func__ << " successfully locked " << dev_cb->addr; + cset_cb->cur_lock_res.addr.push_back(dev_cb->addr); + cset_cb->cur_lock_res.value = LOCK_VALUE; + srvc->lock = LOCK_VALUE; + // add app_id against this device entry + srvc->lock_applist.push_back(cset_cb->cur_lock_req.app_id); + } + + //proceed with next set member + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_lock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_send_lock_req_act + * + * Description This function is is used to send LOCK request to set member. + * It validates if it is required to send lock request based on + * connection state and current lock value. + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_send_lock_req_act(tBTA_CSET_CB* cset_cb) { + RawAddress bd_addr; + uint8_t cur_index = cset_cb->cur_lock_req.cur_idx; + + if (cur_index == (uint8_t)cset_cb->cur_lock_req.members_addr.size()) { + LOG(INFO) << __func__ << " lock operation completed for all set members"; + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + bta_csip_get_next_lock_request(cset_cb); + return; + } + + bd_addr = cset_cb->cur_lock_req.members_addr[cur_index]; + + // get device control block and corresponding csis service details + cset_cb->cur_dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + // Skip device if it is not in connected state + if (!cset_cb->cur_dev_cb || !srvc || + cset_cb->cur_dev_cb->state != BTA_CSIP_CONN_ST) { + LOG(INFO) << __func__ << ": Set Member (" << bd_addr.ToString() + << ") is not connected. Skip this Set member"; + + cset_cb->cur_lock_req.cur_idx++; + cset_cb->cur_lock_res.status = SOME_LOCKS_ACQUIRED_REASON_DISC; + bta_csip_send_lock_req_act(cset_cb); + + // check if already locked (skip sending write request) + } else if (srvc->lock == LOCK_VALUE) { + LOG(INFO) << __func__ << ": Set Member (" << cset_cb->cur_dev_cb->addr + << ") is already locked. Skip this Set member"; + cset_cb->cur_lock_res.value = LOCK_VALUE; + // add element in the list + cset_cb->cur_lock_res.addr.push_back(bd_addr); + // add appid in the list if locked by different app + if (!bta_csip_is_member_locked_by_app(cset_cb->cur_lock_req.app_id, srvc)) { + srvc->lock_applist.push_back(cset_cb->cur_lock_req.app_id); + } + cset_cb->cur_lock_req.cur_idx++; + // process next set member + bta_csip_send_lock_req_act(cset_cb); + + // send the lock request + } else { + // Lock value in vector format (one uint8_t size element with ) + LOG(INFO) << __func__ << " Sending Lock Request to "<< cset_cb->cur_dev_cb->addr + << " Conn Id: " << +cset_cb->cur_dev_cb->conn_id; + std::vector lock_value = {2}; + BtaGattQueue::WriteCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, lock_value, GATT_WRITE, bta_csip_lock_req_cb, cset_cb); + + // Start set member request timeout alarm + cset_cb->unresp_timer = alarm_new("csip_unresp_sm_timer"); + alarm_set_on_mloop(cset_cb->unresp_timer, cset_cb->set_member_tout, + bta_csip_set_member_lock_timeout, cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_unlock_req_cb + * + * Description This callback function is called when set member is unlocked + * by remote device after UNLOCK Request + * + * Parameters: GATT operation callback params. + * + ******************************************************************************/ +void bta_csip_unlock_req_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + void* data) { + LOG(INFO) << __func__ << " status = " << +status; + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)data; + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_get_dev_cb_by_cid(conn_id); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(dev_cb, cset_cb->cur_lock_req.set_id); + + /* Device control block or corresponding CSIS service instance not found */ + if (!dev_cb || !srvc) { + APPL_TRACE_ERROR("%s: Device CB not found for conn_id = %d", __func__, conn_id); + cset_cb->cur_lock_req.cur_idx++; + alarm_cancel(cset_cb->unresp_timer); + bta_csip_send_unlock_req_act(cset_cb); + return; + } + + /*check if this response is received from unresponsive set member */ + if (dev_cb->unresponsive) { + LOG(INFO) << __func__ << " unresponsive remote: " << dev_cb->addr; + srvc->lock = UNLOCK_VALUE; + srvc->unrsp_applist.clear(); + dev_cb->unresponsive = false; + return; + } + + // cancel alarm (used for unresponsive set member) + alarm_cancel(cset_cb->unresp_timer); + + /* PTS Test Case: read any characteristic */ + if (status == CSIP_LOCK_RELEASE_NOT_ALLOWED || + status == CSIP_INVALID_LOCK_VALUE) { + /* for PTS to ensure set coordinator is working fine */ + BtaGattQueue::ReadCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, NULL, NULL); + + /* Stop unlocking remaining set members and inform requesting app */ + cset_cb->cur_lock_res.status = status; + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + /* Process next lock request from pending queue */ + bta_csip_get_next_lock_request(cset_cb); + } else { + if (status == GATT_SUCCESS) { + srvc->lock = UNLOCK_VALUE; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), + srvc->lock_applist.end(), cset_cb->cur_lock_req.app_id), + srvc->lock_applist.end()); + cset_cb->cur_lock_res.addr.push_back(dev_cb->addr); + } + //proceed with next set member + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_send_unlock_req_act + * + * Description This function is is used to send UNLOCK request to set member. + * It validates if it is required to send unlock request based on + * connection state and current lock value. + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_send_unlock_req_act(tBTA_CSET_CB* cset_cb) { + RawAddress bd_addr; + uint8_t cur_index = cset_cb->cur_lock_req.cur_idx; + + if (cur_index == (uint8_t)cset_cb->cur_lock_req.members_addr.size()) { + cset_cb->request_in_progress = false; + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + bta_csip_get_next_lock_request(cset_cb); + LOG(INFO) << __func__ << " Request completed for all set members"; + return; + } + + bd_addr = cset_cb->cur_lock_req.members_addr[cur_index]; + + LOG(INFO) << __func__ << ": Set Member address: " << bd_addr.ToString(); + + // get device control block and corresponding csis service details + cset_cb->cur_dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + /* Device control block or corresponding CSIS service instance not found */ + if (!cset_cb->cur_dev_cb || !srvc) { + APPL_TRACE_ERROR("%s: Device CB not found for %s", __func__, + bd_addr.ToString().c_str()); + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + return; + } + + /* Set member is not locked by requesting app */ + if (!bta_csip_is_member_locked_by_app(cset_cb->cur_lock_req.app_id, srvc)) { + LOG(INFO) << __func__ << " App "<< +cset_cb->cur_lock_req.app_id + << "has not locked this set member (" << srvc->bd_addr + << "). Skip this set member"; + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + return; + } + + // Skip device if it is not in connected state + if (cset_cb->cur_dev_cb->state != BTA_CSIP_CONN_ST) { + LOG(INFO) << __func__ << ": Set Member (" << bd_addr.ToString() + << ") is not connected. Skip this Set member"; + + cset_cb->cur_lock_req.cur_idx++; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), srvc->lock_applist.end(), + cset_cb->cur_lock_req.app_id), srvc->lock_applist.end()); + bta_csip_send_unlock_req_act(cset_cb); + + // check if already unlocked or locked by multiple apps (skip sending write request) + } else if (srvc->lock == UNLOCK_VALUE || + bta_csip_is_locked_by_other_apps(srvc, cset_cb->cur_lock_req.app_id)) { + LOG(INFO) << __func__ << ": Set Member (" << bd_addr.ToString() + << ") is already unlocked or locked by other app. Skip this Set member"; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), srvc->lock_applist.end(), + cset_cb->cur_lock_req.app_id), srvc->lock_applist.end()); + cset_cb->cur_lock_req.cur_idx++; + cset_cb->cur_lock_res.addr.push_back(cset_cb->cur_dev_cb->addr); + // process next set member + bta_csip_send_unlock_req_act(cset_cb); + + // send the unlock request + } else { + // Unlock value in vector format (one uint8_t size element with unlock value) + std::vector lock_value(1, UNLOCK_VALUE); + BtaGattQueue::WriteCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, lock_value, GATT_WRITE, bta_csip_unlock_req_cb, cset_cb); + + // Start set member request timeout alarm + cset_cb->unresp_timer = alarm_new("csip_unresp_sm_timer"); + alarm_set_on_mloop(cset_cb->unresp_timer, cset_cb->set_member_tout, + bta_csip_set_member_lock_timeout, cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_set_member_lock_timeout + * + * Description This API is called when Set Member has not responded within + * required set member lock timeout. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_set_member_lock_timeout(void* p_data) { + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)p_data; + tBTA_CSIP_DEV_CB* dev_cb = cset_cb->cur_dev_cb; + + APPL_TRACE_DEBUG("%s", __func__); + // Device not found or disconnected + if (!dev_cb || dev_cb->state != BTA_CSIP_CONN_ST) { + LOG(ERROR) << __func__ << " device disconnected."; + cset_cb->cur_lock_res.status = SOME_LOCKS_ACQUIRED_REASON_DISC; + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_lock_req_act(cset_cb); + return; + } + + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + if (!srvc) { + APPL_TRACE_ERROR("%s: CSIS instance not found.", __func__); + return; + } + + dev_cb->unresponsive = true; + // add app_id in unresponsive set members app list + srvc->unrsp_applist.push_back(cset_cb->cur_lock_res.app_id); + + if (cset_cb->cur_lock_req.value == LOCK_VALUE) { + cset_cb->cur_lock_res.status = SOME_LOCKS_ACQUIRED_REASON_TIMEOUT; + APPL_TRACE_DEBUG("%s: Process next device in the lock request", __func__); + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_lock_req_act(cset_cb); + } else if (cset_cb->cur_lock_req.value == UNLOCK_VALUE) { + cset_cb->cur_lock_res.addr.push_back( + cset_cb->cur_lock_req.members_addr[cset_cb->cur_lock_req.cur_idx]); + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_le_encrypt_cback + * + * Description link encryption complete callback. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_le_encrypt_cback(const RawAddress* bd_addr, + UNUSED_ATTR tGATT_TRANSPORT transport, + UNUSED_ATTR void* p_ref_data, tBTM_STATUS result) { + APPL_TRACE_ERROR("%s: status = %d", __func__, result); + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(*bd_addr); + + if (!p_cb) { + APPL_TRACE_ERROR("unexpected encryption callback, ignore"); + return; + } + + /* If encryption fails, disconnect the connection */ + if (result != BTM_SUCCESS) { + bta_csip_close_csip_conn(p_cb); + return; + } + + if (p_cb->state == BTA_CSIP_W4_SEC) { + bta_csip_sm_execute(p_cb, BTA_CSIP_ENC_CMPL_EVT, NULL); + } +} + +/******************************************************************************* + * + * Function bta_csip_open_act + * + * Description API Call to open CSIP Gatt Connection + * + * Returns None + * + ******************************************************************************/ +void bta_csip_api_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s: Open GATT connection for CSIP", __func__); + + tBTA_CSIP_API_CONN* p_conn_req = (tBTA_CSIP_API_CONN *)&p_data->conn_param; + + if (!bta_csip_is_app_reg(p_conn_req->app_id)) { + LOG(ERROR) << __func__ << ": Request from Invalid/Unregistered App: " + << +p_conn_req->app_id; + if (p_cb && (uint8_t)p_cb->conn_applist.size() == 0) { + p_cb->state = BTA_CSIP_IDLE_ST; + } + // No need to send callback to invalid/unregistered app + return; + } + + if (btm_sec_is_a_bonded_dev(p_cb->addr) && !bta_csip_is_csis_supported(p_cb)) { + APPL_TRACE_DEBUG("%s: Remote (%s) doesnt contain any coordinated set", __func__, + p_cb->addr.ToString().c_str()); + bta_csip_send_conn_state_changed_cb(p_cb, p_conn_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_COORDINATED_SET_NOT_SUPPORTED); + return; + } + + if (!p_cb) { + LOG(ERROR) << __func__ << ": Insufficient resources. Max" + " supported Set members have reached "; + tBTA_CSIP_DEV_CB invalid_cb = { + .addr = p_data->conn_param.bd_addr + }; + bta_csip_send_conn_state_changed_cb(&invalid_cb, p_conn_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_CONN_ESTABLISHMENT_FAILED); + return; + } + + // check if connection state is already connected + if (p_cb->state == BTA_CSIP_CONN_ST) { + if (!bta_csip_is_app_from_applist(p_cb, p_conn_req->app_id)) { + bta_csip_add_app_to_applist(p_cb, p_conn_req->app_id); + } + bta_csip_send_conn_state_changed_cb(p_cb, p_conn_req->app_id, + BTA_CSIP_CONNECTED, BTA_CSIP_CONN_ESTABLISHED); + return; + + // other app has already started connection procedure + } else if (!bta_csip_is_app_from_applist(p_cb, p_conn_req->app_id) + && (uint8_t)p_cb->conn_applist.size() > 0 + && p_cb->state != BTA_CSIP_IDLE_ST) { + LOG(INFO) << __func__ << ": Other app is establishing CSIP Connection." + << " Current connection state = " << +p_cb->state; + bta_csip_add_app_to_applist(p_cb, p_conn_req->app_id); + /* Note: Callback will given to all apps once connection procedure is completed */ + return; + } + + p_cb->addr = p_data->conn_param.bd_addr; + bta_csip_add_app_to_applist(p_cb, p_conn_req->app_id); + + BTA_GATTC_Open(bta_csip_cb.gatt_if, p_cb->addr, true, GATT_TRANSPORT_LE, + false); +} + +/******************************************************************************* + * + * Function bta_csip_api_close_act + * + * Description API Call to close CSIP Gatt Connection + * + * Returns None + * + ******************************************************************************/ +void bta_csip_api_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + if (!p_cb) { + LOG(ERROR) << __func__ << " Already Closed"; + return; + } + + tBTA_CSIP_API_CONN* p_req = (tBTA_CSIP_API_CONN *)&p_data->conn_param; + + LOG(INFO) << __func__ << " Disconnect Request from App: " << +p_req->app_id; + + if (!bta_csip_is_app_reg(p_req->app_id)) { + LOG(ERROR) << __func__ << ": Request from Invalid/Unregistered App: " + << +p_req->app_id; + // No need to send callback to invalid/unregistered app + return; + } else if (!bta_csip_is_app_from_applist(p_cb, p_req->app_id)) { + LOG(ERROR) << __func__ << " App (ID:"<< +p_req->app_id <<") has not connected"; + bta_csip_send_conn_state_changed_cb(p_cb, p_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_DISCONNECT_WITHOUT_CONNECT); + return; + } + + // Check if its last disconnecting app + if ((uint8_t)p_cb->conn_applist.size() > 1) { + bta_csip_remove_app_from_conn_list(p_cb, p_req->app_id); + bta_csip_send_conn_state_changed_cb(p_cb, p_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_APP_DISCONNECTED); + return; + } + + bta_csip_close_csip_conn(p_cb); +} + +/******************************************************************************* + * + * Function bta_csip_gatt_open_act + * + * Description Callback function when GATT Connection is created. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + tBTA_GATTC_OPEN* open_param = &p_data->gatt_open_param; + LOG(INFO) << __func__ << " Remote = " << open_param->remote_bda + << " Status = " << open_param->status + << " conn_id = " << open_param->conn_id; + + if (open_param->status == GATT_SUCCESS) { + p_cb->in_use = true; + p_cb->conn_id = open_param->conn_id; + BtaGattQueue::Clean(p_cb->conn_id); + bta_csip_sm_execute(p_cb, BTA_CSIP_START_ENC_EVT, NULL); + } else { + /* open failure */ + bta_csip_sm_execute(p_cb, BTA_CSIP_OPEN_FAIL_EVT, p_data); + } +} + +/******************************************************************************* + * + * Function bta_csip_gatt_close_act + * + * Description Callback function when GATT Connection is closed. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + tBTA_GATTC_OPEN* open_param = &p_data->gatt_open_param; + + // Give callback to all apps from connection applist + bta_csip_send_conn_state_changed_cb(p_cb, BTA_CSIP_DISCONNECTED, open_param->status); + + // Clear applist + p_cb->conn_applist.clear(); + + for (int i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++) { + tBTA_CSIS_SRVC_INFO* srvc = &p_cb->csis_srvc[i]; + if (srvc->in_use) { + srvc->lock = UNLOCK_VALUE; + } + } + + p_cb->conn_id = 0; + p_cb->in_use = false; +} + +/******************************************************************************* + * + * Function bta_csip_gatt_open_fail_act + * + * Description Callback function when GATT Connection fails to be created. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_open_fail_act (tBTA_CSIP_DEV_CB* p_cb, + tBTA_CSIP_REQ_DATA* p_data) { + LOG(ERROR) << __func__ << " Failed to open GATT Connection"; + + tBTA_GATTC_OPEN* open_param = &p_data->gatt_open_param; + + // Give callback to all apps from connection applist waiting for connection + bta_csip_send_conn_state_changed_cb(p_cb, BTA_CSIP_DISCONNECTED, open_param->status); + + // Clear applist + p_cb->conn_applist.clear(); + p_cb->in_use = false; +} + +/******************************************************************************* + * + * Function bta_csip_open_cmpl_act + * + * Description Tasks needed to be done when connection is established. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_open_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s", __func__); + + if (!p_cb) { + LOG(ERROR) << __func__ << " Invalid device contrl block"; + return; + } + + // Give callback to all apps from connection applist waiting for connection + bta_csip_send_conn_state_changed_cb(p_cb, BTA_CSIP_CONNECTED, + BTA_CSIP_CONN_ESTABLISHED); + + /* Register for notification of required CSIS characteristic*/ + int i = 0; + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++) { + tBTA_CSIS_SRVC_INFO* srvc = &p_cb->csis_srvc[i]; + if (srvc->in_use) { + bta_csip_write_cccd(p_cb, srvc->lock_handle, srvc->lock_ccd_handle); + bta_csip_write_cccd(p_cb, srvc->size_handle, srvc->size_ccd_handle); + bta_csip_write_cccd(p_cb, srvc->sirk_handle, srvc->sirk_ccd_handle); + } + } + +} + +/******************************************************************************* + * + * Function bta_csip_start_sec_act + * + * Description Tasks needed to be done to check or establish CSIP required + * security. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_start_sec_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s", __func__); + + uint8_t sec_flag = 0; + + // Get security flags for the device + BTM_GetSecurityFlagsByTransport(p_cb->addr, &sec_flag, BT_TRANSPORT_LE); + + // link is already encrypted, send encryption complete callback to csip + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + LOG(INFO) << __func__ << " Already Encrypted"; + bta_csip_sm_execute(p_cb, BTA_CSIP_ENC_CMPL_EVT, NULL); + } + // device is bonded but link is not encrypted. Start encryption + else if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + sec_flag = BTM_BLE_SEC_ENCRYPT; + BTM_SetEncryption(p_cb->addr, BTA_TRANSPORT_LE, bta_csip_le_encrypt_cback, + NULL, sec_flag); + } + // unbonded device. Set MITM Encryption + else if (p_cb->sec_mask != BTA_SEC_NONE) { + sec_flag = BTM_BLE_SEC_ENCRYPT_MITM; + BTM_SetEncryption(p_cb->addr, BTA_TRANSPORT_LE, bta_csip_le_encrypt_cback, + NULL, sec_flag); + } + // link is already encrypted + else { + bta_csip_sm_execute(p_cb, BTA_CSIP_ENC_CMPL_EVT, NULL); + } +} + +/******************************************************************************* + * + * Function bta_csip_start_sec_act + * + * Description Tasks needed to be done to check or establish CSIP required + * security. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_sec_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s p_cb->csis_srvc[0].in_use = %d, p_cb->csis_srvc[0].sirk_handle = %d", + __func__, p_cb->csis_srvc[0].in_use, p_cb->csis_srvc[0].sirk_handle); + + if (!p_cb->csis_srvc[0].in_use || !p_cb->csis_srvc[0].sirk_handle) { + /* Service discovery is triggered from this path when csip connection is opened + * from 3rd party application */ + LOG(INFO) << __func__ << "Service discovery is pending"; + Uuid pri_srvc = Uuid::From16Bit(UUID_SERVCLASS_CSIS); + BTA_GATTC_ServiceSearchRequest(p_cb->conn_id, &pri_srvc); + } else { + LOG(INFO) << __func__ << "Service discovery is already completed"; + bta_csip_sm_execute(p_cb, BTA_CSIP_OPEN_CMPL_EVT, NULL); + } +} + +/******************************************************************************* + * + * Function bta_csip_close_csip_conn + * + * Description API to close CSIP Connection and remove device from background + * list. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_close_csip_conn (tBTA_CSIP_DEV_CB* p_cb) { + LOG(INFO) << __func__; + if (p_cb->conn_id != GATT_INVALID_CONN_ID) { + // clear pending GATT Requests + BtaGattQueue::Clean(p_cb->conn_id); + p_cb->state = BTA_CSIP_DISCONNECTING_ST; + // Send Close to GATT Layer + if (p_cb->state == BTA_CSIP_CONN_ST || p_cb->conn_id) { + BTA_GATTC_Close(p_cb->conn_id); + } else { + BTA_GATTC_CancelOpen(bta_csip_cb.gatt_if, p_cb->addr, true); + tBTA_GATTC_OPEN open = {.status = GATT_SUCCESS}; + bta_csip_gatt_close_act(p_cb,(tBTA_CSIP_REQ_DATA *)&open); + } + } +} + +/******************************************************************************* + * + * Function bta_csip_handle_notification + * + * Description This function is called when notification is received on one + * of the characteristic registered for notification. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_handle_notification(tBTA_GATTC_NOTIFY* ntf) { + if (!ntf->is_notify) return; + LOG(INFO) << __func__<< " Set Member: " << ntf->bda << ", handle: " << ntf->handle; + + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(ntf->bda); + if (!p_cb) { + LOG(ERROR) << __func__ << " No CSIP GATT Connection for this device"; + return; + } + + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(p_cb->conn_id, ntf->handle); + if (p_char == NULL) { + APPL_TRACE_ERROR( + "%s: notification received for Unknown Characteristic, conn_id: " + "0x%04x, handle: 0x%04x", + __func__, p_cb->conn_id, ntf->handle); + return; + } + + if (p_char->uuid == CSIS_SERVICE_LOCK_UUID) { + bta_csip_handle_lock_value_notif(p_cb, ntf->handle, ntf->value[0]); + } else if (p_char->uuid == CSIS_SERVICE_SIRK_UUID) { + //bta_csip_handle_sirk_change(); + } else if (p_char->uuid == CSIS_SERVICE_SIZE_UUID) { + //bta_csip_handle_size_change(); + } +} + +/******************************************************************************* + * + * Function bta_csip_handle_lock_value_notif + * + * Description This function is called when notification is received for + * change in lock value on set member. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_handle_lock_value_notif(tBTA_CSIP_DEV_CB* p_cb, + uint16_t handle, uint8_t value) { + tBTA_CSIS_SRVC_INFO* srvc = bta_csip_find_csis_srvc_by_lock_handle(p_cb, handle); + if (!srvc) { + LOG(ERROR) << __func__ << " CSIS Service instance not found for this handle"; + return; + } + + /* LOCK has been released by Set member (by lock timeout) */ + if (value == UNLOCK_VALUE && srvc->lock == LOCK_VALUE) { + srvc->lock = UNLOCK_VALUE; + /* Give lock status changed notification to all apps holding + * lock for this set member */ + LOG(INFO) << __func__ << " Lock released by timeout"; + for (auto i: srvc->lock_applist) { + tBTA_CSIP_RCB* rcb = bta_csip_get_rcb(i); + if (rcb && rcb->p_cback) { + std::vector sm(1, p_cb->addr); + tBTA_LOCK_STATUS_CHANGED p_data = {i, srvc->set_id, value, + LOCK_RELEASED_TIMEOUT, sm}; + (*rcb->p_cback) (BTA_CSIP_LOCK_STATUS_CHANGED_EVT, (tBTA_CSIP_DATA *)&p_data); + } + } + srvc->lock_applist.clear(); + } + /* LOCK held by other set coordinator is released */ + else if (value == UNLOCK_VALUE && srvc->lock == UNLOCK_VALUE) { + // check if lock was denied for any previous request + for (auto i: srvc->denied_applist) { + tBTA_CSIP_RCB* rcb = bta_csip_get_rcb(i); + if (rcb && rcb->p_cback) { + tBTA_LOCK_AVAILABLE p_data = {i, srvc->set_id, p_cb->addr}; + (*rcb->p_cback) (BTA_CSIP_LOCK_AVAILABLE_EVT, (tBTA_CSIP_DATA *)&p_data); + } + } + srvc->denied_applist.clear(); + } + /* Other Set Coordinator acquired the lock */ + else if (value == LOCK_VALUE && srvc->lock == UNLOCK_VALUE) { + // No action is required to be taken + } +} + +/******************************************************************************* + * + * Function bta_csip_csis_disc_complete_ind + * + * Description This function informas CSIS service discovery has been + * completed to DM layer. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_csis_disc_complete_ind (RawAddress& addr) { + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(addr); + if (p_cb) { + p_cb->total_instance_disc++; + LOG(INFO) << __func__ << " discovered = " << +p_cb->total_instance_disc + << " Total = " << +p_cb->csis_instance_count; + if (p_cb->total_instance_disc == p_cb->csis_instance_count + && !p_cb->is_disc_external) { + bta_dm_csis_disc_complete(addr, true); + bta_dm_lea_disc_complete(addr); + } + } +} + +/******************************************************************************* + * + * Function bta_csip_give_new_set_found_cb + * + * Description Give new coordinate set found callback to upper layer. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_give_new_set_found_cb (tBTA_CSIS_SRVC_INFO *srvc) { + /* Check if this remote csis instance is included in another service */ + const std::vector* services = + BTA_GATTC_GetServices(srvc->conn_id); + + if (services) { + for (const gatt::Service& service : *services) { + if (service.is_primary) { + for (const gatt::IncludedService &included_srvc : service.included_services) { + if (included_srvc.uuid == CSIS_SERVICE_UUID + && service.handle == srvc->service_handle) { + APPL_TRACE_DEBUG("%s: service Uuid of service including CSIS service : %s", + __func__, service.uuid.ToString().c_str()); + srvc->including_srvc_uuid = service.uuid; + } + } + } + } + } + + // Given New Set found callback to upper layer + tBTA_CSIP_NEW_SET_FOUND new_set_params; + new_set_params.set_id = srvc->set_id; + memcpy(new_set_params.sirk, srvc->sirk, SIRK_SIZE); + new_set_params.size = srvc->size; + new_set_params.including_srvc_uuid = srvc->including_srvc_uuid; + new_set_params.addr = srvc->bd_addr; + new_set_params.lock_support = (srvc->lock_handle != 0)? true : false; + + (*bta_csip_cb.p_cback)(BTA_CSIP_NEW_SET_FOUND_EVT, (tBTA_CSIP_DATA *)&new_set_params); +} + +bool bta_csip_decrypt_sirk(tBTA_CSIS_SRVC_INFO *srvc, uint8_t *enc_sirk) { + // Get K from LTK or Link Key based on transport + Octet16 K = {}; + uint8_t gatt_if, transport = BT_TRANSPORT_LE; + RawAddress bdaddr; + GATT_GetConnectionInfor(srvc->conn_id, &gatt_if, bdaddr, &transport); + + char sample_data_prop[6]; + osi_property_get("vendor.bt.pts.sample_csis_data", sample_data_prop, "false"); + + if (!strncmp("true", sample_data_prop, 4)) { // comparing prop with "true" + K = {0x67, 0x6e, 0x1b, 0x9b, 0xd4, 0x48, 0x69, 0x6f, + 0x06, 0x1e, 0xc6, 0x22, 0x3c, 0xe5, 0xce, 0xd9}; + } else if (transport == BT_TRANSPORT_BR_EDR) { + K = BTM_SecGetDeviceLinkKey(srvc->bd_addr); + } else if (transport == BT_TRANSPORT_LE) { + RawAddress pseudo_addr; + pseudo_addr = bta_get_pseudo_addr_with_id_addr(srvc->bd_addr); + Octet16 rev_K = BTM_BleGetLTK(pseudo_addr); + std::reverse_copy(rev_K.begin(), rev_K.end(), K.begin()); + } + + if(is_key_empty(K)) { + APPL_TRACE_DEBUG("%s Invalid Key received", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_KEY; + return false; + } + + /* compute SALT */ + Octet16 salt = bta_csip_get_salt(); + + // Compute T + Octet16 T = bta_csip_compute_T(salt, K); + + // Compute final result k1 + Octet16 k1 = bta_csip_compute_k1(T); + + // Get decrypted SIRK + Octet16 r_k1; + std::reverse_copy(k1.begin(), k1.end(), r_k1.begin()); + bta_csip_get_decrypted_sirk(r_k1, enc_sirk, srvc->sirk); + return true; +} + +/******************************************************************************* + * + * Function bta_sirk_read_cb + * + * Description Callback received when remote device Coordinated Sets SIRK + * is read. + * + * Returns None + * + ******************************************************************************/ +void bta_sirk_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + APPL_TRACE_DEBUG("%s ", __func__); + + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: SIRK Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + tBTA_CSIS_SRVC_INFO *srvc = (tBTA_CSIS_SRVC_INFO *)data; + + uint8_t type = 0xFF; + LOG(INFO) << __func__ << " SIRK len = " << +len; + + if (len != (SIRK_SIZE + 1)) { + APPL_TRACE_ERROR("%s : Invalid SIRK length", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_SIRK_FORMAT; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + + STREAM_TO_UINT8(type, value); + APPL_TRACE_DEBUG("%s Type Field with SIRK = %d", __func__, type); + if (type != ENCRYPTED_SIRK && type != PLAINTEXT_SIRK) { + APPL_TRACE_ERROR("%s : Invalid SIRK Type", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_KEY_TYPE; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + + if (type == ENCRYPTED_SIRK) { + uint8_t enc_sirk[SIRK_SIZE] = {}; + STREAM_TO_ARRAY(enc_sirk, value, SIRK_SIZE); + if (!bta_csip_decrypt_sirk(srvc, enc_sirk)) { + APPL_TRACE_ERROR("%s : Invalid Empty Key", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_KEY; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + } else { + STREAM_TO_ARRAY(srvc->sirk, value, SIRK_SIZE); + } + // check if this set was found earlier + uint8_t set_id = bta_csip_find_set_id_by_sirk (srvc->sirk); + tBTA_CSET_CB *cset_cb = NULL; + + /* New Coordinated Set */ + if (set_id == INVALID_SET_ID) { + cset_cb = bta_csip_get_cset_cb(); + if (!cset_cb) { + LOG(ERROR) << __func__ << " Insufficient set control blocks available."; + srvc->discovery_status = BTA_CSIP_RSRC_EXHAUSTED; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + memcpy(cset_cb->sirk, srvc->sirk, SIRK_SIZE); + + // Create new coordinated set and update in database + tBTA_CSIP_CSET cset = {}; + cset.set_id = cset_cb->set_id; + cset.set_members.push_back(srvc->bd_addr); + cset.total_discovered++; + cset.lock_support = (srvc->lock_handle != 0 ? true : false); + LOG(INFO) << __func__ << "New Set. Adding device " << srvc->bd_addr.ToString() + << " Set ID: " << +cset.set_id; + bta_csip_cb.csets.push_back(cset); + + // assign set id in respective control blocks + srvc->set_id = cset_cb->set_id; + + /* Existing coordinated Set */ + } else { + LOG(INFO) << __func__ << " Device from existing set (set_id: " << +set_id << " )"; + //bta_csip_csis_disc_complete_ind(srvc->bd_addr); + srvc->set_id = set_id; + if (!bta_csip_update_set_member(set_id, srvc->bd_addr)) { + srvc->discovery_status = BTA_CSIP_ALL_MEMBERS_DISCOVERED; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + + // Give set member found callback + tBTA_SET_MEMBER_FOUND set_member_params = + { .set_id = set_id, + .addr = srvc->bd_addr, + }; + + bta_csip_cb.p_cback (BTA_CSIP_SET_MEMBER_FOUND_EVT, (tBTA_CSIP_DATA *)&set_member_params); + return; + } + + /* If size is optional, give callback to upper layer */ + if (!srvc->size_handle) { + bta_csip_give_new_set_found_cb(srvc); + } + + if (!srvc->size_handle && !srvc->rank_handle) { + bta_csip_preserve_cset(srvc); + } +} + +/******************************************************************************* + * + * Function bta_size_read_cb + * + * Description Callback received when remote device Coordinated Sets SIZE + * characteristic is read. + * + * Returns None + * + ******************************************************************************/ +void bta_size_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: SIZE Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + tBTA_CSIS_SRVC_INFO *srvc = (tBTA_CSIS_SRVC_INFO *)data; + + if (srvc->discovery_status != BTA_CSIP_DISC_SUCCESS) { + APPL_TRACE_ERROR("%s: Ignore response (Reason: %d)", __func__, srvc->discovery_status); + return; + } + + srvc->size = *value; + APPL_TRACE_DEBUG("%s size = %d", __func__, srvc->size); + + tBTA_CSIP_CSET* cset = bta_csip_get_or_create_cset(srvc->set_id, true); + if (cset) cset->size = srvc->size; + // Give callback only when its a first set member + uint8_t totalDiscovered = bta_csip_get_coordinated_set(srvc->set_id).set_members.size(); + if (totalDiscovered == 1) { + bta_csip_give_new_set_found_cb(srvc); + } + + if (!srvc->rank_handle) { + bta_csip_preserve_cset(srvc); + } +} + +/******************************************************************************* + * + * Function bta_lock_read_cb + * + * Description Callback received when remote device Coordinated Sets LOCK + * characteristic is read. + * + * Returns None + * + ******************************************************************************/ +void bta_lock_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: LOCK Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + APPL_TRACE_DEBUG("%s lock value = %d", __func__, *value); +} + +/******************************************************************************* + * + * Function bta_rank_read_cb + * + * Description Callback received when remote device Coordinated Sets RANK + * characteristic is read. + * + * Returns None + * + ******************************************************************************/ +void bta_rank_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: Rank Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + tBTA_CSIS_SRVC_INFO *srvc = (tBTA_CSIS_SRVC_INFO *)data; + if (srvc->discovery_status != BTA_CSIP_DISC_SUCCESS) { + APPL_TRACE_ERROR("%s: Ignore response (Reason: %d)", __func__, srvc->discovery_status); + return; + } + + srvc->rank = *value; + APPL_TRACE_DEBUG("%s device: %s Rank = %d set_id: %d", __func__, + srvc->bd_addr.ToString().c_str(), srvc->rank, srvc->set_id); + + // get coordinated set control block from set_id + tBTA_CSET_CB *cset_cb = bta_csip_get_cset_cb_by_id(srvc->set_id); + if (cset_cb) { + cset_cb->ordered_members.insert({srvc->rank, srvc->bd_addr}); + } + + bta_csip_preserve_cset(srvc); + bta_csip_csis_disc_complete_ind(srvc->bd_addr); +} + +/******************************************************************************* + * + * Function bta_csip_gatt_disc_cmpl_act + * + * Description This APIS is used to serach presence of csis service on + * remote device and initialize CSIS handles in csis service + * control block. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_disc_cmpl_act(tBTA_CSIP_DISC_SET *disc_params) { + uint16_t conn_id = disc_params->conn_id; + uint8_t status = disc_params->status; + RawAddress addr = disc_params->addr; + + APPL_TRACE_DEBUG("%s conn_id = %d, status = %d addr: %s", __func__, conn_id, + status, addr.ToString().c_str()); + + if (status) return; + + // Fetch remote device gatt services from database + const std::vector* services = + BTA_GATTC_GetServices(conn_id); + + if (!services) { + LOG(ERROR) << __func__ << " No Services discovered."; + bta_csip_csis_disc_complete_ind(addr); + return; + } + + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_find_dev_cb_by_bda(addr); + + if (!dev_cb) { + dev_cb = bta_csip_create_dev_cb_for_bda(addr); + } + + dev_cb->csis_instance_count = 0; + + // Search for CSIS service in the database + for (const gatt::Service& service : *services) { + if (service.uuid == CSIS_SERVICE_UUID) { + dev_cb->csis_instance_count++; + // Get service control block from service handle (subsequent connection) + tBTA_CSIS_SRVC_INFO *srvc = bta_csip_get_csis_service_by_handle(dev_cb, service.handle); + if (!srvc) { + // create new service cb (if its a first time connection) + srvc = bta_csip_get_csis_service_cb(dev_cb); + if (!srvc) { + APPL_TRACE_ERROR("%s Resources not available for storing CSIS Service.", __func__); + return; + } + } + srvc->bd_addr = addr; + srvc->service_handle = service.handle; + srvc->conn_id = conn_id; + APPL_TRACE_DEBUG("%s: CSIS service found Uuid: %s service_handle = %d", __func__, + service.uuid.ToString().c_str(), srvc->service_handle); + + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid uuid1 = charac.uuid; + if (uuid1 == CSIS_SERVICE_SIRK_UUID) { + srvc->sirk_handle = charac.value_handle; + srvc->sirk_ccd_handle = bta_csip_get_cccd_handle(conn_id, charac.value_handle); + } else if (uuid1 == CSIS_SERVICE_SIZE_UUID) { + srvc->size_handle = charac.value_handle; + srvc->size_ccd_handle = bta_csip_get_cccd_handle(conn_id, charac.value_handle); + } else if (uuid1 == CSIS_SERVICE_LOCK_UUID) { + srvc->lock_handle = charac.value_handle; + srvc->lock_ccd_handle = bta_csip_get_cccd_handle(conn_id, charac.value_handle); + } else if (uuid1 == CSIS_SERVICE_RANK_UUID) { + srvc->rank_handle = charac.value_handle; + } + } + + /* Skip reading characteristics and Set Discovery procedure if it was done earlier */ + if (srvc->set_id >= 0 && srvc->set_id < BTA_MAX_SUPPORTED_SETS) { + LOG(INFO) << __func__ << " Coordinated set discovery procedure already completed."; + continue; + } + + if (srvc->sirk_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->sirk_handle, bta_sirk_read_cb, srvc); + } + + if (srvc->size_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->size_handle, bta_size_read_cb, srvc); + } + + if (srvc->lock_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->lock_handle, bta_lock_read_cb, srvc); + } + + if (srvc->rank_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->rank_handle, bta_rank_read_cb, srvc); + } + } + + } +} diff --git a/le_audio/system/bt/bta/csip/bta_csip_api.cc b/le_audio/system/bt/bta/csip/bta_csip_api.cc new file mode 100644 index 0000000000000000000000000000000000000000..73a9054c9667a37c8d347608e4dc424ea57d5340 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_api.cc @@ -0,0 +1,287 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +/****************************************************************************** + * + * This file contains the CSIP API in the subsystem of BTA. + * + ******************************************************************************/ + +#define LOG_TAG "bt_bta_csip" + +#include +#include +#include +#include +#include +#include + +#include "bta_csip_api.h" +#include "bta_csip_int.h" +#include "bta_gatt_queue.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" + +/***************************************************************************** + * Constants + ****************************************************************************/ +static const tBTA_SYS_REG bta_csip_reg = {bta_csip_hdl_event, BTA_CsipDisable}; + +/********************************************************************************* + * + * Function BTA_RegisterCsipApp + * + * Description This function is called to register application or module to + * to register with CSIP for using CSIP functionalities. + * + * Parameters p_csip_cb: callback to be received in registering app when + * required CSIP operation is completed. + * reg_cb : callback when app/module is registered with CSIP. + * + * Returns None + * + *********************************************************************************/ +void BTA_RegisterCsipApp(tBTA_CSIP_CBACK* p_csip_cb, + BtaCsipAppRegisteredCb reg_cb) { + do_in_bta_thread(FROM_HERE, base::Bind(&bta_csip_app_register, Uuid::GetRandom(), + p_csip_cb, std::move(reg_cb))); +} + +/********************************************************************************* + * + * Function BTA_UnregisterCsipApp + * + * Description This function is called to unregister application or module. + * + * Parameters app_id: id of the app/module that needs to be unregistered. + * + * Returns None + * + *********************************************************************************/ + +void BTA_UnregisterCsipApp(uint8_t app_id) { + do_in_bta_thread(FROM_HERE, base::Bind(&bta_csip_app_unregister, app_id)); +} + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters lock_param: parameters to acquire or release lock. + * (tBTA_SET_LOCK_PARAMS). + * + * Returns None + * + *********************************************************************************/ +void BTA_CsipSetLockValue(tBTA_SET_LOCK_PARAMS lock_params) { + tBTA_CSIP_LOCK_PARAMS* p_buf = + (tBTA_CSIP_LOCK_PARAMS*)osi_calloc(sizeof(tBTA_CSIP_LOCK_PARAMS)); + + p_buf->hdr.event = BTA_CSIP_SET_LOCK_VALUE_EVT; + p_buf->lock_req = lock_params; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipGetCoordinatedSet + * + * Description This function is called to fetch details of the coordinated set. + * + * Parameters set_id: identifier of the coordinated set whose details are + * required to be fetched. + * + * Returns tBTA_CSIP_CSET (containing details of coordinated set). + * + *********************************************************************************/ +tBTA_CSIP_CSET BTA_CsipGetCoordinatedSet(uint8_t set_id) { + APPL_TRACE_DEBUG("%s: set_id = %d", __func__, set_id); + return bta_csip_get_coordinated_set(set_id); +} + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters None. + * + * Returns vector: (all discovered coordinated set) + * + *********************************************************************************/ +std::vector BTA_CsipGetDiscoveredSets() { + return bta_csip_cb.csets; +} + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipConnect (uint8_t app_id, const RawAddress& bd_addr) { + tBTA_CSIP_API_CONN* p_buf = + (tBTA_CSIP_API_CONN*)osi_calloc(sizeof(tBTA_CSIP_API_CONN)); + p_buf->hdr.event = BTA_CSIP_API_OPEN_EVT; + p_buf->bd_addr = bd_addr; + p_buf->app_id = app_id; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipDisconnect (uint8_t app_id, const RawAddress& bd_addr) { + tBTA_CSIP_API_CONN* p_buf = + (tBTA_CSIP_API_CONN*)osi_calloc(sizeof(tBTA_CSIP_API_CONN)); + p_buf->hdr.event = BTA_CSIP_API_CLOSE_EVT; + p_buf->bd_addr = bd_addr; + p_buf->app_id = app_id; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipFindCsisInstance + * + * Description This function is called to find presence of CSIS service on + * remote device. + * + * Parameters coon_id : Connection ID of the GATT Connection at DM Layer. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipFindCsisInstance(uint16_t conn_id, tGATT_STATUS status, + RawAddress& bd_addr) { + APPL_TRACE_DEBUG("%s ", __func__); + + tBTA_CSIP_DISC_SET* p_buf = + (tBTA_CSIP_DISC_SET*)osi_calloc(sizeof(tBTA_CSIP_DISC_SET)); + p_buf->hdr.event = BTA_CSIP_DISC_CMPL_EVT; + p_buf->conn_id = conn_id; + p_buf->status = status; + p_buf->addr = bd_addr; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipInit + * + * Description This function is invoked to initialize CSIP in BTA layer. + * + * Parameters None. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipEnable(tBTA_CSIP_CBACK *p_cback) { + tBTA_CSIP_ENABLE* p_buf = + (tBTA_CSIP_ENABLE*)osi_calloc(sizeof(tBTA_CSIP_ENABLE)); + + /* register with BTA system manager */ + bta_sys_register(BTA_ID_GROUP, &bta_csip_reg); + + p_buf->hdr.event = BTA_CSIP_API_ENABLE_EVT; + p_buf->p_cback = p_cback; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipDisable + * + * Description This function is called for deinitialization. + * + * Parameters None. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipDisable() { + tBTA_CSIP_ENABLE* p_buf = + (tBTA_CSIP_ENABLE*)osi_calloc(sizeof(tBTA_CSIP_ENABLE)); + + p_buf->hdr.event = BTA_CSIP_API_DISABLE_EVT; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipRemoveUnpairedSetMember + * + * Description This function is called when a given set member is unpaired. + * + * Parameters addr: BD Address of the set member. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipRemoveUnpairedSetMember(RawAddress addr) { + do_in_bta_thread(FROM_HERE, base::Bind(&bta_csip_remove_set_member, addr)); +} + +/********************************************************************************* + * + * Function BTA_CsipGetDeviceSetId + * + * Description This API is used to get set id of the remote device. + * + * Parameters addr: BD Address of the set member. + * uuid: UUID of the service which includes CSIS service. + * + * Returns None. + * + *********************************************************************************/ +uint8_t BTA_CsipGetDeviceSetId(RawAddress addr, bluetooth::Uuid uuid) { + for (tBTA_CSIP_CSET cset: bta_csip_cb.csets) { + for (RawAddress bd_addr: cset.set_members) { + if (bd_addr == addr && (cset.p_srvc_uuid == uuid)) { + return cset.set_id; + } + } + } + + return BTA_MAX_SUPPORTED_SETS; +} diff --git a/le_audio/system/bt/bta/csip/bta_csip_int.h b/le_audio/system/bt/bta/csip/bta_csip_int.h new file mode 100644 index 0000000000000000000000000000000000000000..5000bbf6bccacac03cb9ce045bc489e720a852e9 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_int.h @@ -0,0 +1,321 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +/****************************************************************************** + * + * This file contains BTA CSIP Client internal definitions + * + ******************************************************************************/ + +#ifndef BTA_CSIP_INT_H +#define BTA_CSIP_INT_H + +#include "bta_csip_api.h" +#include "bta_gatt_api.h" +#include "bta_sys.h" +#include "btm_ble_api_types.h" + +/* Max CSIP supported devices (control blocks) */ +#define BTA_CSIP_MAX_DEVICE 32 + +/* Max CSIP supported coordinated sets */ +#define BTA_MAX_SUPPORTED_SETS 16 + +/* Maximum number of apps that can be registered with CSIP*/ +#define BTA_CSIP_MAX_SUPPORTED_APPS 16 + +/* Max Supported coordinated sets per device*/ +#define MAX_SUPPORTED_SETS_PER_DEVICE 5 + +/* Status of the CSIS Discovery*/ +#define BTA_CSIP_DISC_SUCCESS 0 +#define BTA_CSIP_INVALID_SIRK_FORMAT 1 +#define BTA_CSIP_INVALID_KEY 2 +#define BTA_CSIP_INVALID_KEY_TYPE 3 +#define BTA_CSIP_ALL_MEMBERS_DISCOVERED 4 +#define BTA_CSIP_RSRC_EXHAUSTED 5 + +using bluetooth::Uuid; + +/* state machine events, these events are handled by the state machine */ +enum { + BTA_CSIP_API_OPEN_EVT = BTA_SYS_EVT_START(BTA_ID_GROUP), + BTA_CSIP_API_CLOSE_EVT, + BTA_CSIP_GATT_OPEN_EVT, + BTA_CSIP_GATT_CLOSE_EVT, + BTA_CSIP_OPEN_FAIL_EVT, + BTA_CSIP_OPEN_CMPL_EVT, + BTA_CSIP_START_ENC_EVT, + BTA_CSIP_ENC_CMPL_EVT, + BTA_CSIP_GATT_ENC_CMPL_EVT, + + /* common events: not handled by execute state machine */ + BTA_CSIP_API_ENABLE_EVT, + BTA_CSIP_API_DISABLE_EVT, + BTA_CSIP_DISC_CMPL_EVT, + BTA_CSIP_SET_LOCK_VALUE_EVT, +}; + +/* CSIP device state machine states */ +enum { + BTA_CSIP_IDLE_ST, + BTA_CSIP_W4_CONN_ST, + BTA_CSIP_W4_SEC, + BTA_CSIP_CONN_ST, + BTA_CSIP_DISCONNECTING_ST, +}; + +typedef uint8_t tBTA_CSIP_STATE; + + +/* CSIP Command request parameters in BTA */ + +/* Find Coordinated set parameters*/ +typedef struct { + BT_HDR hdr; + uint16_t conn_id; + tGATT_STATUS status; + RawAddress addr; +} tBTA_CSIP_DISC_SET; + +/* Connection Request parameters */ +typedef struct { + BT_HDR hdr; + RawAddress bd_addr; + uint8_t app_id; +} tBTA_CSIP_API_CONN; + +/* Lock request parameters */ +typedef struct { + BT_HDR hdr; + tBTA_SET_LOCK_PARAMS lock_req; +} tBTA_CSIP_LOCK_PARAMS; + + +typedef struct { + BT_HDR hdr; + tBTA_CSIP_CBACK* p_csip_cb; + tBTA_CSIP_CLT_REG_CB* reg_cb; +} tBTA_CSIP_APP_REG_PARAMS; + +typedef struct { + BT_HDR hdr; + tBTA_CSIP_CBACK *p_cback; +} tBTA_CSIP_ENABLE; + +typedef struct { + BT_HDR hdr; +} tBTA_CSIP_CMD; + +typedef union { + BT_HDR hdr; + tBTA_CSIP_API_CONN conn_param; + tBTA_CSIP_LOCK_PARAMS lock_req; + tBTA_CSIP_APP_REG_PARAMS reg_param; + tBTA_CSIP_ENABLE enable_param; + tBTA_GATTC_OPEN gatt_open_param; + tBTA_GATTC_CLOSE gatt_close_param; + tBTA_CSIP_CMD cmd; +} tBTA_CSIP_REQ_DATA; + +typedef struct { + bool in_use; + uint8_t set_id = BTA_MAX_SUPPORTED_SETS; + uint16_t conn_id; /* GATT conn_id used for service discovery */ + RawAddress bd_addr; + + uint16_t service_handle; /* Handle of this CSIS service */ + uint16_t sirk_handle; /* SIRK characteristic value handle */ + uint16_t size_handle; /* size characteristic value handle */ + uint16_t lock_handle; /* lock characteristic value handle */ + uint16_t rank_handle; /* rank characteristic value handle */ + + uint16_t sirk_ccd_handle; /* SIRK CCCD handle*/ + uint16_t size_ccd_handle; /* size CCCD handle */ + uint16_t lock_ccd_handle; /* lock CCCD handle */ + + uint8_t sirk[SIRK_SIZE]; /* Coordinated set SIRK */ + uint8_t size; /* size of the coordinated set */ + uint8_t lock; /* lock status of the set member */ + uint8_t rank; /* rank of the set member*/ + uint8_t discovery_status = BTA_CSIP_DISC_SUCCESS; /* status of the CSIS discovery*/ + bluetooth::Uuid including_srvc_uuid; /* uuid of the service which includes CSIS*/ + + /* Lock mamangement details*/ + std::vector lock_applist; /* Apps those have locked this set */ + std::vector unrsp_applist; /* Apps to which unresponsive res is sent */ + std::vector denied_applist; /* Apps to which lock was denied */ +} tBTA_CSIS_SRVC_INFO; + +typedef struct { + bool in_use; + RawAddress addr; /* Remote device address */ + uint16_t conn_id; /* GATT Connection ID */ + uint8_t sec_mask = (BTM_SEC_IN_ENCRYPT | BTM_SEC_OUT_ENCRYPT); /* Security Mask for CSIP*/ + bool security_pending; + bool is_disc_external = false; /* if discovery is started by external App*/ + uint8_t state; /* connection state */ + uint8_t csis_instance_count; /* number of CSIS instances on remote device */ + uint8_t total_instance_disc; /* total number of instances discovered */ + + /* CSIS services found on remote device*/ + tBTA_CSIS_SRVC_INFO csis_srvc[MAX_SUPPORTED_SETS_PER_DEVICE]; + + // list of applications which initiated CSIP connect for this device + std::vector conn_applist; /* List of Apps that sent connection request*/ + std::string set_info = ""; + bool unresponsive; /* if remote is unresponsive to GATT request */ +} tBTA_CSIP_DEV_CB; + +typedef struct { + uint8_t app_id; + uint8_t set_id; + uint8_t value; + int8_t cur_idx; + std::vector members_addr; +} tBTA_LOCK_REQUEST; + +typedef struct { + bool in_use; + uint8_t set_id = BTA_MAX_SUPPORTED_SETS; + uint8_t sirk[SIRK_SIZE]; + uint16_t set_member_tout = 500; + bool request_in_progress; + tBTA_CSIP_DEV_CB* cur_dev_cb; + alarm_t* unresp_timer; + tBTA_LOCK_REQUEST cur_lock_req; + tBTA_LOCK_STATUS_CHANGED cur_lock_res; + std::map ordered_members; + std::queue lock_req_queue; +} tBTA_CSET_CB; + +typedef struct { + uint8_t app_id; + bool in_use; + tBTA_CSIP_CBACK* p_cback; +} tBTA_CSIP_RCB; + +typedef struct { + tGATT_IF gatt_if; + tBTA_CSIP_CBACK* p_cback; /* callbacks for btif layer */ + std::vector dev_cb; /* device control block */ + tBTA_CSIP_RCB app_rcb[BTA_CSIP_MAX_SUPPORTED_APPS]; + std::vector csets; + tBTA_CSET_CB csets_cb[BTA_MAX_SUPPORTED_SETS]; +} tBTA_CSIP_CB; + +/***************************************************************************** + * Global data + ****************************************************************************/ + +/* CSIP control block */ +extern tBTA_CSIP_CB bta_csip_cb; + +/***************************************************************************** + * Function prototypes + ****************************************************************************/ +void bta_csip_sm_execute(tBTA_CSIP_DEV_CB* p_cb, uint16_t event, + tBTA_CSIP_REQ_DATA* p_data); + + +//action api's +extern bool bta_csip_hdl_event(BT_HDR* p_msg); +extern void bta_csip_api_enable(tBTA_CSIP_CBACK *p_cback); +extern void bta_csip_api_disable(); +extern void bta_csip_app_register (const Uuid& app_uuid, tBTA_CSIP_CBACK* p_cback, + BtaCsipAppRegisteredCb cb); +extern void bta_csip_gattc_register(); +extern void bta_csip_app_unregister(uint8_t app_id); +extern void bta_csip_gatt_disc_cmpl_act(tBTA_CSIP_DISC_SET *disc_params); +extern void bta_csip_api_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_api_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_gatt_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_gatt_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_gatt_open_fail_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_open_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_start_sec_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_sec_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_close_csip_conn (tBTA_CSIP_DEV_CB* p_cb); +extern void bta_csip_process_set_lock_act(tBTA_SET_LOCK_PARAMS lock_req); +extern void bta_csip_send_lock_req_act(tBTA_CSET_CB* cset_cb); +extern void bta_csip_handle_lock_denial(tBTA_CSET_CB* cset_cb); +extern bool bta_csip_validate_req_for_denied_sm (tBTA_CSET_CB* cset_cb); +extern void bta_csip_form_lock_request(tBTA_SET_LOCK_PARAMS lock_param, + tBTA_CSET_CB* cset_cb); +extern void bta_csip_send_unlock_req_act(tBTA_CSET_CB* cset_cb); +extern void bta_csip_set_member_lock_timeout(void* p_data); +extern void bta_csip_load_coordinated_sets_from_storage(); + +// bta_csip_utils +extern tBTA_CSIP_DEV_CB* bta_csip_find_dev_cb_by_bda(const RawAddress& bda); +extern tBTA_CSIP_DEV_CB* bta_csip_get_dev_cb_by_cid(uint16_t conn_id); +extern tBTA_CSIP_DEV_CB* bta_csip_create_dev_cb_for_bda(const RawAddress& bda); +extern tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_cb(tBTA_CSIP_DEV_CB* dev_cb); +extern bool bta_csip_is_csis_supported(tBTA_CSIP_DEV_CB* dev_cb); +extern tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_by_handle(tBTA_CSIP_DEV_CB* dev_cb, + uint16_t service_handle); +extern tBTA_CSIS_SRVC_INFO* bta_csip_find_csis_srvc_by_lock_handle(tBTA_CSIP_DEV_CB* dev_cb, + uint16_t lock_handle); +extern tBTA_CSIP_CSET* bta_csip_get_or_create_cset (uint8_t set_id, bool existing); +extern bool bta_csip_validate_set_params(tBTA_SET_LOCK_PARAMS* lock_req); +extern bool bta_csip_is_valid_lock_request(tBTA_SET_LOCK_PARAMS* lock_req); +extern std::vector bta_csip_arrange_set_members_by_order( + uint8_t set_id, std::vector& req_sm, bool ascending); +extern tBTA_CSIP_CSET bta_csip_get_coordinated_set (uint8_t set_id); +extern bool bta_csip_update_set_member (uint8_t set_id, RawAddress addr); +extern tBTA_CSET_CB* bta_csip_get_cset_cb (); +extern tBTA_CSET_CB* bta_csip_get_cset_cb_by_id (uint8_t set_id); +extern uint8_t bta_csip_find_set_id_by_sirk (uint8_t* sirk); +extern tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_instance(tBTA_CSIP_DEV_CB* dev_cb, uint8_t set_id); +extern bool bta_csip_is_locked_by_other_apps(tBTA_CSIS_SRVC_INFO* srvc, uint8_t app_id); +extern std::vector bta_csip_get_set_member_by_order(uint8_t set_id, + bool ascending); +extern bool bta_csip_is_member_locked_by_app (uint8_t app_id, tBTA_CSIS_SRVC_INFO* srvc); +extern void bta_csip_get_next_lock_request(tBTA_CSET_CB* cset_cb); +extern uint16_t bta_csip_get_cccd_handle (uint16_t conn_id, uint16_t char_handle); +extern bool bta_csip_is_app_reg(uint8_t app_id); +extern tBTA_CSIP_RCB* bta_csip_get_rcb (uint8_t app_id); +extern void bta_csip_remove_set_member (RawAddress addr); +extern void bta_csip_handle_notification(tBTA_GATTC_NOTIFY* ntf); +extern void bta_csip_handle_lock_value_notif(tBTA_CSIP_DEV_CB* p_cb, + uint16_t handle, uint8_t value); +extern void bta_csip_add_app_to_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id); +extern void bta_csip_handle_unresponsive_sm_res(tBTA_CSIS_SRVC_INFO* srvc, + tGATT_STATUS status); +extern void bta_csip_remove_app_from_conn_list(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id); +extern bool bta_csip_is_app_from_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id); +extern void bta_csip_send_conn_state_changed_cb(tBTA_CSIP_DEV_CB* p_cb, + uint8_t state, uint8_t status); +extern void bta_csip_send_conn_state_changed_cb (tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id, + uint8_t state, uint8_t status); +extern void bta_csip_send_lock_req_cmpl_cb (tBTA_LOCK_STATUS_CHANGED cset_cb); +extern void bta_csip_write_cccd (tBTA_CSIP_DEV_CB* p_cb, uint16_t char_handle, + uint16_t cccd_handle); +extern void bta_csip_preserve_cset (tBTA_CSIS_SRVC_INFO* srvc); +extern Octet16 bta_csip_get_salt(); +extern Octet16 bta_csip_compute_T(Octet16 salt, Octet16 K); +extern Octet16 bta_csip_compute_k1(Octet16 T); +extern void bta_csip_get_decrypted_sirk(Octet16 k1, uint8_t *enc_sirk, uint8_t *sirk); +extern Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const Octet16& message); +extern Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const uint8_t* input, + uint16_t length); +extern void hex_string_to_byte_arr(char *str, uint8_t* byte_arr, uint8_t len); +extern void byte_arr_to_hex_string(uint8_t* byte_arr, char* str, uint8_t len); +extern bool is_key_empty(Octet16& key); +#endif diff --git a/le_audio/system/bt/bta/csip/bta_csip_main.cc b/le_audio/system/bt/bta/csip/bta_csip_main.cc new file mode 100644 index 0000000000000000000000000000000000000000..87111f19b0228682c106e3583a31a3ece9215234 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_main.cc @@ -0,0 +1,296 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "bt_target.h" +#include "bta_csip_int.h" + +#include + +#include "bt_common.h" + +#define LOG_TAG "bt_bta_csip" + +/***************************************************************************** + * Static methods + ****************************************************************************/ +static const char* bta_csip_evt_code(uint16_t evt_code); +static const char* bta_csip_state_code(tBTA_CSIP_STATE evt_code); + +/***************************************************************************** + * Global data + ****************************************************************************/ +tBTA_CSIP_CB bta_csip_cb; + +enum { + BTA_CSIP_OPEN_ACT, + BTA_CSIP_CLOSE_ACT, + BTA_CSIP_GATT_OPEN_ACT, + BTA_CSIP_GATT_CLOSE_ACT, + BTA_CSIP_GATT_OPEN_FAIL_ACT, + BTA_CSIP_OPEN_CMPL_ACT, + BTA_CSIP_START_SEC_ACT, + BTA_CSIP_SEC_CMPL_ACT, + BTA_CSIP_IGNORE, +}; + +/* type for action functions */ +typedef void (*tBTA_CSIP_ACTION)(tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); + +/* action functions */ +const tBTA_CSIP_ACTION bta_csip_action[] = { + bta_csip_api_open_act, + bta_csip_api_close_act, + bta_csip_gatt_open_act, + bta_csip_gatt_close_act, + bta_csip_gatt_open_fail_act, + bta_csip_open_cmpl_act, + bta_csip_start_sec_act, + bta_csip_sec_cmpl_act, +}; + +/* state table information */ +#define BTA_CSIP_ACTION 0 /* position of action */ +#define BTA_CSIP_NEXT_STATE 1 /* position of next state */ +#define BTA_CSIP_NUM_COLS 2 /* number of columns */ + +/* state table in idle state */ +const uint8_t bta_csip_st_idle[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_OPEN_CMPL_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, +}; + +/* state table in wait for security state */ +const uint8_t bta_csip_st_w4_conn[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_CLOSE_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_GATT_OPEN_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_GATT_OPEN_FAIL_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_OPEN_CMPL_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_START_SEC_ACT, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_W4_CONN_ST}, +}; + +/* state table in wait for connection state */ +const uint8_t bta_csip_st_w4_sec[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_CLOSE_ACT, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_GATT_OPEN_FAIL_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_OPEN_CMPL_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_SEC_CMPL_ACT, BTA_CSIP_W4_CONN_ST}, +}; + +/* state table in connection state */ +const uint8_t bta_csip_st_connected[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_CLOSE_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_GATT_OPEN_FAIL_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, +}; + +/* state table in disconnecting state */ +const uint8_t bta_csip_st_disconnecting[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, +}; + +/* type for state table */ +typedef const uint8_t (*tBTA_CSIP_ST_TBL)[BTA_CSIP_NUM_COLS]; + +/* state table */ +tBTA_CSIP_ST_TBL bta_csip_st_tbl[] = {bta_csip_st_idle, bta_csip_st_w4_conn, + bta_csip_st_w4_sec, bta_csip_st_connected, + bta_csip_st_disconnecting}; + +/******************************************************************************* + * + * Function bta_csip_sm_execute + * + * Description API to execute state operation. + * + * Returns void + * + ******************************************************************************/ +void bta_csip_sm_execute(tBTA_CSIP_DEV_CB* p_cb, uint16_t event, + tBTA_CSIP_REQ_DATA* p_data) { + tBTA_CSIP_ST_TBL state_table; + uint8_t action; + + if (!p_cb) { + APPL_TRACE_ERROR("%s: Device not found. Return.", __func__); + return; + } + + state_table = bta_csip_st_tbl[p_cb->state]; + + event &= 0xff; + + p_cb->state = state_table[event][BTA_CSIP_NEXT_STATE]; + APPL_TRACE_DEBUG("%s: Next State = %d(%s) event = %04x(%s)", __func__, + p_cb->state, bta_csip_state_code(p_cb->state), event, + bta_csip_evt_code(event)); + + action = state_table[event][BTA_CSIP_ACTION]; + APPL_TRACE_DEBUG("%s: action = %d", __func__, action); + if (action != BTA_CSIP_IGNORE) { + (*bta_csip_action[action])(p_cb, p_data); + } + +} + +/******************************************************************************* + * + * Function bta_csip_hdl_event + * + * Description CSIP client main event handling function. + * + * Returns void + * + ******************************************************************************/ +bool bta_csip_hdl_event(BT_HDR* p_msg) { + tBTA_CSIP_DEV_CB* dev_cb = NULL; + + APPL_TRACE_DEBUG("%s: Event: %04x", __func__, p_msg->event); + + switch (p_msg->event) { + case BTA_CSIP_API_ENABLE_EVT: + bta_csip_api_enable(((tBTA_CSIP_ENABLE *)p_msg)->p_cback); + break; + + case BTA_CSIP_API_DISABLE_EVT: + bta_csip_api_disable(); + break; + + case BTA_CSIP_DISC_CMPL_EVT: + bta_csip_gatt_disc_cmpl_act((tBTA_CSIP_DISC_SET *)p_msg); + break; + + case BTA_CSIP_SET_LOCK_VALUE_EVT: + bta_csip_process_set_lock_act(((tBTA_CSIP_LOCK_PARAMS*)p_msg)->lock_req); + break; + + default: + if (p_msg->event == BTA_CSIP_API_OPEN_EVT) { + RawAddress bd_addr = ((tBTA_CSIP_API_CONN *)p_msg)->bd_addr; + dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + if (!dev_cb) { + dev_cb = bta_csip_create_dev_cb_for_bda(bd_addr); + APPL_TRACE_DEBUG("%s: Created Device CB for device: %s", + __func__, bd_addr.ToString().c_str()); + } + } else if (p_msg->event == BTA_CSIP_API_CLOSE_EVT) { + dev_cb = bta_csip_find_dev_cb_by_bda(((tBTA_CSIP_API_CONN *)p_msg)->bd_addr); + } + + bta_csip_sm_execute(dev_cb, p_msg->event, (tBTA_CSIP_REQ_DATA*)p_msg); + } + + return (true); + +} + +/******************************************************************************* + * + * Function bta_csip_evt_code + * + * Description returns event name in string format + * + * Returns string representation of event code + * + ******************************************************************************/ +static const char* bta_csip_evt_code(uint16_t evt_code) { + evt_code = (BTA_ID_GROUP << 8) | evt_code; + switch (evt_code) { + case BTA_CSIP_API_OPEN_EVT: + return "BTA_CSIP_API_OPEN_EVT"; + case BTA_CSIP_API_CLOSE_EVT: + return "BTA_CSIP_API_CLOSE_EVT"; + case BTA_CSIP_GATT_OPEN_EVT: + return "BTA_CSIP_GATT_OPEN_EVT"; + case BTA_CSIP_GATT_CLOSE_EVT: + return "BTA_CSIP_GATT_CLOSE_EVT"; + case BTA_CSIP_OPEN_FAIL_EVT: + return "BTA_CSIP_OPEN_FAIL_EVT"; + case BTA_CSIP_OPEN_CMPL_EVT: + return "BTA_CSIP_OPEN_CMPL_EVT"; + case BTA_CSIP_START_ENC_EVT: + return "BTA_CSIP_START_ENC_EVT"; + case BTA_CSIP_ENC_CMPL_EVT: + return "BTA_CSIP_ENC_CMPL_EVT"; + case BTA_CSIP_API_ENABLE_EVT: + return "BTA_CSIP_API_ENABLE_EVT"; + case BTA_CSIP_API_DISABLE_EVT: + return "BTA_CSIP_API_DISABLE_EVT"; + case BTA_CSIP_SET_LOCK_VALUE_EVT: + return "BTA_CSIP_SET_LOCK_VALUE_EVT"; + default: + return "Unknown CSIP event code"; + } +} + +/******************************************************************************* + * + * Function bta_csip_state_code + * + * Description returns state name in string format + * + * Returns string representation of connection state + * + ******************************************************************************/ +static const char* bta_csip_state_code(tBTA_CSIP_STATE state) { + switch (state) { + case BTA_CSIP_IDLE_ST: + return "BTA_CSIP_IDLE_ST"; + case BTA_CSIP_W4_CONN_ST: + return "BTA_CSIP_W4_CONN_ST"; + case BTA_CSIP_W4_SEC: + return "BTA_CSIP_W4_SEC"; + case BTA_CSIP_CONN_ST: + return "BTA_CSIP_CONN_ST"; + case BTA_CSIP_DISCONNECTING_ST: + return "BTA_CSIP_DISCONNECTING_ST"; + default: + return "Incorrect State"; + } +} + diff --git a/le_audio/system/bt/bta/csip/bta_csip_utils.cc b/le_audio/system/bt/bta/csip/bta_csip_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..b900c3abbf589ac28e25c228fe46d4aa11f264cb --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_utils.cc @@ -0,0 +1,1318 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +/****************************************************************************** + * + * This file contains the CSIP Client supporting functions + * + ******************************************************************************/ + +#include +#include +#include + +#include + +#include "bta_csip_api.h" +#include "bta_csip_int.h" +#include "bta_gatt_queue.h" + +#include "osi/include/config.h" +#include "btif/include/btif_config.h" +#include "stack/crypto_toolbox/crypto_toolbox.h" + +/* CSIS Characteristic descriptors handles */ +#define CSIP_CCCD_UUID_VAL 0x2902 +Uuid CSIP_CCCD_UUID = Uuid::From16Bit(CSIP_CCCD_UUID_VAL); + +/******************************************************************************* + * + * Function bta_csip_validate_set_params + * + * Description Validates if set id and its members are valid + * + * Returns bool. true - if details are valid otherwise false. + * + ******************************************************************************/ +bool bta_csip_validate_set_params(tBTA_SET_LOCK_PARAMS* lock_req) { + std::vector *csets = &bta_csip_cb.csets; + tBTA_CSIP_CSET cset; + bool is_valid_set = false; + + std::vector::iterator itr; + for (itr = csets->begin(); itr != csets->end(); ++itr) { + if (lock_req->set_id == itr->set_id) { + cset = *itr; + is_valid_set = true; + break; + } + } + + if (!is_valid_set) { + LOG(ERROR) << __func__ << ": Invalid Set ID = " << +lock_req->set_id; + //TODO: Give Invalid parameters callback + return (false); + } + + std::vector req_members = lock_req->members_addr; + // TODO: if requested set members size = 0, return true + if ((int)lock_req->members_addr.size() == 0) { + LOG(INFO) << __func__<< " Lock of All Set Memebers is requested"; + return (true); + } + + std::vector set_members = cset.set_members; + int members_matched = 0; + for (int i = 0; i < (int)req_members.size(); i++) { + for (int j = 0; j < (int)set_members.size(); j++) { + if (req_members[i] == set_members[j]) { + members_matched++; + break; + } + } + } + LOG(INFO) << "set members matched count = " << +members_matched; //debug + if (members_matched != (int)req_members.size()) { + LOG(ERROR) << __func__ << " Incorrect Set members provided"; + return (false); + } + + return (true); +} + +/******************************************************************************* + * + * Function bta_csip_is_valid_lock_request + * + * Description Validates lock request parameters received + * + * Returns bool. true - if details are valid otherwise false. + * + ******************************************************************************/ +bool bta_csip_is_valid_lock_request(tBTA_SET_LOCK_PARAMS* lock_req) { + // validate if correct lock value is provided + if (lock_req->lock_value != UNLOCK_VALUE && lock_req->lock_value != LOCK_VALUE) { + LOG(ERROR) << __func__ << ": Invalid Lock Value."; + return (false); + } + + // validate set id + if (!bta_csip_validate_set_params(lock_req)) { + LOG(INFO) << __func__ << " Invalid params"; + return (false); + } + + return (true); +} + +/******************************************************************************* + * + * Function bta_csip_get_cset_cb + * + * Description Finds coordinated set control block by set_id + * + * Returns tBTA_CSET_CB. NULL - if set is not found. + * + ******************************************************************************/ +tBTA_CSET_CB* bta_csip_get_cset_cb_by_id (uint8_t set_id) { + int i; + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[0]; + + for (i = 0; i < BTA_MAX_SUPPORTED_SETS; i++, cset_cb++) { + if ((cset_cb->in_use) && (cset_cb->set_id == set_id)) { + return (cset_cb); + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_get_cset_cb + * + * Description Creates new coordinated set control block with next available + * set id. + * + * Returns tBTA_CSET_CB. NULL - if no resources are available for set. + * + ******************************************************************************/ +tBTA_CSET_CB* bta_csip_get_cset_cb () { + int i; + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[0]; + + for (i = 0; i < BTA_MAX_SUPPORTED_SETS; i++, cset_cb++) { + if (!cset_cb->in_use) { + cset_cb->set_id = i; + cset_cb->in_use = true; + return (cset_cb); + } + } + + LOG(ERROR) << __func__ << " No resource available for Coordinated set"; + return (NULL); +} + +/******************************************************************************** + * + * Function bta_csip_is_app_reg + * + * Description Utility function to check if app_id is valid and registered. + * + * Returns true - if reistered. + * false - if invalid app id or its not registered. + * + *******************************************************************************/ +bool bta_csip_is_app_reg(uint8_t app_id) { + if (app_id >= BTA_CSIP_MAX_SUPPORTED_APPS) { + return (false); + } + + if (bta_csip_cb.app_rcb[app_id].in_use) { + return (true); + } + + return (false); +} + +/******************************************************************************** + * + * Function bta_csip_get_rcb + * + * Description Utility function to check if app_id is valid and registered. + * + * Returns registration control block. NULL if not in use. + * + *******************************************************************************/ +tBTA_CSIP_RCB* bta_csip_get_rcb (uint8_t app_id) { + if (app_id >= BTA_CSIP_MAX_SUPPORTED_APPS) { + return (NULL); + } + + if (bta_csip_cb.app_rcb[app_id].in_use) { + return (&bta_csip_cb.app_rcb[app_id]); + } + + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_get_coordinated_set + * + * Description Creates new coordinated set control block + * + * Returns tBTA_CSIP_CSET for valid set_id. + * Empty set with INVALID_SET_ID if not found. + * + ******************************************************************************/ +tBTA_CSIP_CSET bta_csip_get_coordinated_set (uint8_t set_id) { + for (tBTA_CSIP_CSET cset: bta_csip_cb.csets) { + if (cset.set_id == set_id) { + return cset; + } + } + + LOG(ERROR) << __func__ << "Coordinated set not found for set_id: " << +set_id; + tBTA_CSIP_CSET cset = {.set_id = INVALID_SET_ID, + .size = 0 + }; + return cset; + } + +/****************************************************************************** + * + * Function bta_csip_update_set_member + * + * Description Updates set member in the given set. + * + * Returns bool (true, if added successfully. Otherwise, false.) + * + ******************************************************************************/ +bool bta_csip_update_set_member (uint8_t set_id, RawAddress addr) { + for (tBTA_CSIP_CSET& cset: bta_csip_cb.csets) { + if (cset.set_id == set_id) { + if (cset.set_members.size() == cset.size) { + LOG(ERROR) << __func__ << " All Set members already discovered."; + return false; + } + cset.set_members.push_back(addr); + return true; + } + } + + LOG(ERROR) << __func__ << "Coordinated set not found for set_id: " << +set_id; + return false; +} + +/******************************************************************************* + * + * Function bta_csip_remove_set_member + * + * Description Removes set member from given coordinated set after unpairing. + * If its the last set member in set, coordinated set is deleted. + * + * Returns void + * + ******************************************************************************/ +void bta_csip_remove_set_member (RawAddress addr) { + LOG(INFO) << __func__ << " Device = " << addr.ToString(); + bool is_device_found = false; + + btif_config_remove(addr.ToString().c_str(), "DGroup"); + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(addr); + if (!p_cb) { + APPL_TRACE_DEBUG("%s: Set Member not found", __func__); + return; + } + + tBTA_CSIS_SRVC_INFO* srvc = &p_cb->csis_srvc[0]; + for (int i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE && !is_device_found; i++, srvc++) { + if (!srvc->in_use) continue; + for (tBTA_CSIP_CSET& cset: bta_csip_cb.csets) { + if (cset.set_id == srvc->set_id) { + //std::remove(cset.set_members.begin(), cset.set_members.end(), addr); + cset.set_members.erase( + std::remove_if(cset.set_members.begin(), cset.set_members.end(), + [&](RawAddress const & bdaddr) { + return bdaddr == addr; + }), + cset.set_members.end()); + is_device_found = true; + LOG(INFO) << __func__ << " Size = " << +(int)cset.set_members.size(); + if (cset.set_members.empty()) { + tBTA_CSET_CB* cset_cb = bta_csip_get_cset_cb_by_id(cset.set_id); + if (cset_cb) { + LOG(INFO) << __func__ << " Invalidating set. Last member unpaired."; + cset_cb->in_use = false; + cset_cb->set_id = INVALID_SET_ID; + cset.set_members.clear(); + bta_csip_cb.csets.erase( + std::remove_if(bta_csip_cb.csets.begin(), + bta_csip_cb.csets.end(), [&](tBTA_CSIP_CSET& cs) { + return cs.set_id == srvc->set_id; + }), + bta_csip_cb.csets.end()); + } + } + break; + } + } + } + + bta_csip_cb.dev_cb.erase( + std::remove_if(bta_csip_cb.dev_cb.begin(), bta_csip_cb.dev_cb.end(), + [&](tBTA_CSIP_DEV_CB const &dev_cb) { + return dev_cb.addr == addr; + }), + bta_csip_cb.dev_cb.end()); +} + +/******************************************************************************* + * + * Function bta_csip_get_or_create_cset + * + * Description API used to create Coordinated Set Control block. + * + * Returns void + * + ******************************************************************************/ +tBTA_CSIP_CSET* bta_csip_get_or_create_cset (uint8_t set_id, bool existing) { + /*std::find_if(bta_csip_cb.csets.begin(), bta_csip_cb.csets.end(), + [&set_id](const tBTA_CSIP_CSET& set) { + return set.set_id == set_id; + });*/ + + for (tBTA_CSIP_CSET& cset: bta_csip_cb.csets) { + if (cset.set_id == set_id) { + return &cset; + } + } + + if (existing) return NULL; + + /* Create a new set with invalid set_id*/ + tBTA_CSIP_CSET cset = {.set_id = INVALID_SET_ID, + .size = 0 + }; + bta_csip_cb.csets.push_back(cset); + return &bta_csip_cb.csets.back(); +} + +/******************************************************************************* + * + * Function bta_csip_find_set_id_by_sirk + * + * Description Finds Coordinated set control block by sirk + * + * Returns set_id if SIRK is found + * otherwise, INVALID_SET_ID + * + ******************************************************************************/ +uint8_t bta_csip_find_set_id_by_sirk (uint8_t* sirk) { + int i = 0; + tBTA_CSET_CB* csets = &bta_csip_cb.csets_cb[0]; + + for (i = 0; i < BTA_MAX_SUPPORTED_SETS; i++, csets++) { + if (csets->in_use) { + // compare SIRK's + if (!memcmp(sirk, csets->sirk, SIRK_SIZE)) { + return csets->set_id; + } + } + } + + return INVALID_SET_ID; +} + + +/******************************************************************************* + * + * Function bta_csip_get_cset_cb + * + * Description Finds coordinated set control block by set_id + * + * Returns tBTA_CSET_CB. NULL - if set is not found. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_instance(tBTA_CSIP_DEV_CB* dev_cb, + uint8_t set_id) { + int i = 0; + + if (!dev_cb) return NULL; + + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + srvc = &dev_cb->csis_srvc[i]; + if ((srvc->in_use) && (srvc->set_id == set_id)) { + return (srvc); + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_get_csis_service_cb + * + * Description Creates new coordinated set control block for a given device + * + * Returns tBTA_CSET_CB. NULL if no resources available. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_cb(tBTA_CSIP_DEV_CB* dev_cb) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (!srvc->in_use) { + srvc->in_use = true; + return srvc; + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_is_csis_supported + * + * Description checks if remote device supports coordinated set + * + * Returns true if supported, otherwise false. + * + ******************************************************************************/ +bool bta_csip_is_csis_supported(tBTA_CSIP_DEV_CB* dev_cb) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (srvc->in_use) { + return true; + } + } + + /* no csis instance found */ + return (false); +} + +/******************************************************************************* + * + * Function bta_csip_get_csis_service_by_handle + * + * Description Gives CSIS Service Control block by service handle. + * + * Returns CSIS Service control block. Null if not found. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_by_handle( + tBTA_CSIP_DEV_CB* dev_cb, uint16_t service_handle) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (srvc->in_use && srvc->service_handle == service_handle) { + return srvc; + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_find_csis_srvc_by_lock_handle + * + * Description Gives CSIS Service Control block by lock handle. + * + * Returns CSIS Service control block. Null if not found. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_find_csis_srvc_by_lock_handle( + tBTA_CSIP_DEV_CB* dev_cb, uint16_t lock_handle) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (srvc->in_use && srvc->lock_handle == lock_handle) { + return srvc; + } + } + + /* no match found */ + return (NULL); + +} + +/******************************************************************************* + * + * Function bta_csip_is_locked_by_other_apps + * + * Description Checks if set is locked by app other than mentioned one. + * + * Returns true, if locked by other app otherwise false. + * + ******************************************************************************/ +bool bta_csip_is_locked_by_other_apps(tBTA_CSIS_SRVC_INFO* srvc, uint8_t app_id) { + std::vector &lock_applist = srvc->lock_applist; + + for (auto& it : lock_applist) { + if (it != app_id) { + return (true); + } + } + + return (false); +} + +/******************************************************************************* + * + * Function bta_csip_form_set_lock_order + * + * Description Forms order of set members as per rank. + * + * Returns Ordered Set members. + * + ******************************************************************************/ +std::vector bta_csip_form_set_lock_order(tBTA_CSET_CB* cset_cb) { + std::vector ordered_members; + std::vector req_members = cset_cb->cur_lock_req.members_addr; + std::map lock_order_map; + + for (int i = 0; i < (int)req_members.size(); i++) { + // get device control block and corresponding csis service details + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_find_dev_cb_by_bda(req_members[i]); + // null checks required + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(dev_cb, cset_cb->cur_lock_req.set_id); + if (srvc) { + lock_order_map.insert({srvc->rank, req_members[i]}); + } + } + + for (auto itr: lock_order_map) { + ordered_members.push_back(itr.second); + } + + return ordered_members; +} + +/******************************************************************************* + * + * Function bta_csip_arrange_set_members_by_order + * + * Description Forms order of set members for LOCK/UNLOCK request. + * + * Returns Ordered set members in vector. + * + ******************************************************************************/ +std::vector bta_csip_arrange_set_members_by_order( + uint8_t set_id, std::vector& req_sm, bool ascending) { + LOG(INFO) << __func__; + + std::vector ordered_req_sm; + std::vector set_members = + bta_csip_get_set_member_by_order(set_id, ascending); + + // Check if all set members are requested + if ((uint8_t)req_sm.size() == 0) { + LOG(INFO) << __func__ << " original size = " << +set_members.size(); + return set_members; + } + + /* LOCK Request Order*/ + for (int i = 0; i < (int)set_members.size(); i++) { + for (int j = 0; j < (int)req_sm.size(); j++) { + if (set_members[i] == req_sm[j]) { + ordered_req_sm.push_back(set_members[i]); + if (ordered_req_sm.size() == req_sm.size()) { + return ordered_req_sm; + } + } + } + } + + return {}; +} + +/******************************************************************************* + * + * Function bta_csip_arrange_set_members_by_order + * + * Description Forms order of set members for LOCK/UNLOCK request. + * + * Returns Ordered set members in vector. + * + ******************************************************************************/ +std::vector bta_csip_get_set_member_by_order(uint8_t set_id, + bool ascending) { + std::vector ordered_members; + + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[set_id]; + if (!cset_cb->in_use) { + LOG(ERROR) << __func__ << " Invalid Set for for Set ID: " << +set_id; + return {}; + } + + if (ascending) { + for (auto itr: cset_cb->ordered_members) { + ordered_members.push_back(itr.second); + } + } else { + for (auto i = cset_cb->ordered_members.rbegin(); + i != cset_cb->ordered_members.rend(); ++i) { + ordered_members.push_back(i->second); + } + } + + return ordered_members; +} + + +/******************************************************************************* + * + * Function bta_csip_is_member_locked_by_app + * + * Description checks if application (app_id) has locked given set. + * + * Returns void. + ******************************************************************************/ +bool bta_csip_is_member_locked_by_app (uint8_t app_id, tBTA_CSIS_SRVC_INFO* srvc) { + std::vector& lock_applist = srvc->lock_applist; + + auto it = std::find(lock_applist.begin(), lock_applist.end(), app_id); + if (it != lock_applist.end()) { + LOG(INFO) << __func__ << " App Id found in app list"; + return true; + } + + LOG(INFO) << __func__ << " App Id not found in app list"; + return false; +} + +/******************************************************************************* + * + * Function bta_csip_handle_unresponsive_sm_res + * + * Description sends lock response to earlier requesting app. + * + * Returns void. + ******************************************************************************/ +void bta_csip_handle_unresponsive_sm_res(tBTA_CSIS_SRVC_INFO* srvc, + tGATT_STATUS status) { + LOG(INFO) << __func__ << " Response from unresponsive remote " << srvc->bd_addr.ToString() + << " status: " << +status; + std::vector& unres_applist = srvc->unrsp_applist; + + if (status == GATT_SUCCESS) { + srvc->lock = LOCK_VALUE; + for (auto& it : unres_applist) { + LOG(INFO) << __func__ << " Sending GATT_SUCCESS callback to app: " << +it; + std::vector sm = {srvc->bd_addr}; + tBTA_LOCK_STATUS_CHANGED res = {.app_id = it, .set_id = srvc->set_id, + .value = 0x02, .addr = sm}; + bta_csip_send_lock_req_cmpl_cb(res); + // Add app id to the lock applist + srvc->lock_applist.push_back(it); + } + } + + unres_applist.clear(); +} + +/******************************************************************************* + * + * Function bta_csip_get_next_lock_request + * + * Description Schedules next pending lock request for the given set. + * + * Returns void. + ******************************************************************************/ +void bta_csip_get_next_lock_request(tBTA_CSET_CB* cset_cb) { + tBTA_SET_LOCK_PARAMS lock_req_params; + std::queue& lock_req_queue = cset_cb->lock_req_queue; + + if (lock_req_queue.empty()) { + LOG(INFO) << " No pending Lock Request for Set: " << +cset_cb->set_id; + cset_cb->request_in_progress = false; + return; + } + + lock_req_params = lock_req_queue.front(); + lock_req_queue.pop(); + + bta_csip_form_lock_request(lock_req_params, cset_cb); +} + +/******************************************************************************* + * + * Function bta_csip_find_dev_cb_by_bda + * + * Description Utility function find a device control block by BD address. + * + * Returns tBTA_CSIP_DEV_CB - device control block for a given remote. + * nullptr, if device control block is not present. + ******************************************************************************/ +tBTA_CSIP_DEV_CB* bta_csip_find_dev_cb_by_bda(const RawAddress& bda) { + /*auto iter = std::find_if(bta_csip_cb.dev_cb.begin(), bta_csip_cb.dev_cb.end(), + [&bda](const tBTA_CSIP_DEV_CB& device) { + return device.addr == bda; + }); + + return (iter == bta_csip_cb.dev_cb.end()) ? nullptr : &(*iter);*/ + for (tBTA_CSIP_DEV_CB& p_cb: bta_csip_cb.dev_cb) { + if (p_cb.addr == bda) { + return &p_cb; + } + } + + return NULL; +} + +/******************************************************************************* + * + * Function bta_csip_get_dev_cb_by_cid + * + * Description Utility function find a device control block by gatt conn id. + * + * Returns tBTA_CSIP_DEV_CB (device control block for a given remote.) + ******************************************************************************/ +tBTA_CSIP_DEV_CB* bta_csip_get_dev_cb_by_cid(uint16_t conn_id) { + auto iter = std::find_if(bta_csip_cb.dev_cb.begin(), bta_csip_cb.dev_cb.end(), + [&conn_id](const tBTA_CSIP_DEV_CB& device) { + return device.conn_id == conn_id; + }); + + return (iter == bta_csip_cb.dev_cb.end()) ? nullptr : &(*iter); +} + +/******************************************************************************* + * + * Function bta_csip_create_dev_cb_for_bda + * + * Description Utility function find a device control block by BD address. + * + * Returns tBTA_CSIP_DEV_CB (device control block for a given remote. + ******************************************************************************/ +tBTA_CSIP_DEV_CB* bta_csip_create_dev_cb_for_bda(const RawAddress& bda) { + tBTA_CSIP_DEV_CB p_dev_cb = {}; + p_dev_cb.addr = bda; + p_dev_cb.in_use = true; + + bta_csip_cb.dev_cb.push_back(p_dev_cb); + + return &bta_csip_cb.dev_cb.back(); +} + +/************************************************************************************ + * + * Function bta_csip_get_cccd_handle + * + * Description Utility function to fetch cccd handle of a given characteristic. + * + * Returns handle of cccd. 0 if not found. + ************************************************************************************/ +uint16_t bta_csip_get_cccd_handle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + if (!p_char) { + LOG(WARNING) << __func__ << ": Characteristic not found: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == CSIP_CCCD_UUID) { + LOG(INFO) << __func__ << " desc handle = " << +desc.handle; + return desc.handle; + } + } + + return 0; +} + +/************************************************************************************ + * + * Function bta_csip_add_app_to_applist + * + * Description Utility function adds app to connection applist of a + * given device control block. + * + * Returns void + ************************************************************************************/ +void bta_csip_add_app_to_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id) { + if (p_cb && !bta_csip_is_app_from_applist(p_cb, app_id)) { + LOG(INFO) << __func__ << ": adding app(" << +app_id + << ") to connection applist of " << p_cb->addr; + p_cb->conn_applist.push_back(app_id); + } +} + +/************************************************************************************ + * + * Function bta_csip_is_app_from_applist + * + * Description Utility function checks if app is from connection applist of a + * given device control block. + * + * Returns true if app has already sent connect request for CSIP. + ************************************************************************************/ +bool bta_csip_is_app_from_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id) { + for (auto i: p_cb->conn_applist) { + if (i == app_id) { + return (true); + } + } + + return (false); +} + +/************************************************************************************ + * + * Function bta_csip_remove_app_from_conn_list + * + * Description Utility function to remove application from connection applist of + * given device control block + * + * Returns void + ************************************************************************************/ +void bta_csip_remove_app_from_conn_list(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id) { + p_cb->conn_applist.erase( + std::remove(p_cb->conn_applist.begin(), p_cb->conn_applist.end(), app_id), + p_cb->conn_applist.end()); +} + +/************************************************************************************ + * + * Function bta_csip_send_conn_state_changed_cb + * + * Description Utility function to send connection state changed to all + * registered application in connection app list. + * + * Returns void + ************************************************************************************/ +void bta_csip_send_conn_state_changed_cb(tBTA_CSIP_DEV_CB* p_cb, + uint8_t state, uint8_t status) { + if (!p_cb) { + LOG(ERROR) << __func__ << ": Device CB for " << p_cb->addr << " not found"; + return; + } + + // send connection state change to all apps in conn_applist + for (auto i: p_cb->conn_applist) { + tBTA_CSIP_CONN_STATE_CHANGED conn_cb_params = { + .app_id = i, + .addr = p_cb->addr, + .state = state, + .status =status + }; + if (bta_csip_cb.app_rcb[i].p_cback) { + (*bta_csip_cb.app_rcb[i].p_cback) + (BTA_CSIP_CONN_STATE_CHG_EVT, (tBTA_CSIP_DATA *)&conn_cb_params); + } + } +} + +/************************************************************************************ + * + * Function bta_csip_send_conn_state_changed_cb + * + * Description Utility function to send connection state changed to requesting + * registered application from connection applist. + * + * Returns void + ************************************************************************************/ +void bta_csip_send_conn_state_changed_cb (tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id, + uint8_t state, uint8_t status) { + + tBTA_CSIP_CONN_STATE_CHANGED conn_cb_params = { + .app_id = app_id, + .addr = p_cb->addr, + .state = state, + .status =status + }; + + // send connection state change to the requested App + if (bta_csip_cb.app_rcb[app_id].p_cback) { + (*bta_csip_cb.app_rcb[app_id].p_cback) + (BTA_CSIP_CONN_STATE_CHG_EVT, (tBTA_CSIP_DATA *)&conn_cb_params); + } + +} + +/************************************************************************************ + * + * Function bta_csip_process_completed_lock_req + * + * Description Utility function to send lock state changed to requesting + * registered application. + * + * Returns void + ************************************************************************************/ +void bta_csip_send_lock_req_cmpl_cb (tBTA_LOCK_STATUS_CHANGED response) { + if (response.app_id >= BTA_CSIP_MAX_SUPPORTED_APPS || + !bta_csip_cb.app_rcb[response.app_id].in_use) { + LOG(ERROR) << __func__ << "Invalid or unregistered application: " << +response.app_id; + return; + } + + if (bta_csip_cb.app_rcb[response.app_id].p_cback) { + (*bta_csip_cb.app_rcb[response.app_id].p_cback) + (BTA_CSIP_LOCK_STATUS_CHANGED_EVT, (tBTA_CSIP_DATA *)&response); + } +} + +/************************************************************************************ + * + * Function bta_csip_write_cccd + * + * Description API used to write required characteristic descriptor. + * + * Returns void + ************************************************************************************/ +void bta_csip_write_cccd (tBTA_CSIP_DEV_CB* p_cb, uint16_t char_handle, + uint16_t cccd_handle) { + LOG(INFO) << __func__; + // Register for LOCK + if (BTA_GATTC_RegisterForNotifications( + bta_csip_cb.gatt_if, p_cb->addr, char_handle)) { + LOG(ERROR) << __func__ + << " Notification Registration failed for char handle: " << +char_handle; + return; + } + + LOG(INFO) << __func__ << " notification registration successful. handle: " << +char_handle; + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + BtaGattQueue::WriteDescriptor(p_cb->conn_id, cccd_handle, value, + GATT_WRITE, nullptr, nullptr); +} + +/************************************************************************************ + * + * Function bta_csip_load_coordinated_sets_from_storage + * + * Description API used to load coordinated sets from storage on BT ON. + * + * Returns void + ************************************************************************************/ +void bta_csip_load_coordinated_sets_from_storage () { + LOG(INFO) << __func__; + + static const char* CONFIG_FILE_PATH = "/data/misc/bluedroid/bt_config.conf"; + config_t* config = config_new(CONFIG_FILE_PATH); + if (!config) { + LOG(INFO) << __func__ << " file "<< CONFIG_FILE_PATH << " not found"; + return; + } + + const config_section_node_t* snode = config_section_begin(config); + while (snode != config_section_end(config)) { + + const char* name = config_section_name(snode); + if (!RawAddress::IsValidAddress(name)) { + snode = config_section_next(snode); + continue; + } + + const char* key = "DGroup"; + const char* coordinated_sets = config_get_string(config, name, key, ""); + if (!strcmp(coordinated_sets, "")) { + LOG(INFO) << __func__ << " doesnt support cooedinated set."; + snode = config_section_next(snode); + continue; + } + + RawAddress bdaddr; + RawAddress::FromString(name, bdaddr); + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_find_dev_cb_by_bda(bdaddr); + if (!dev_cb) { + dev_cb = bta_csip_create_dev_cb_for_bda(bdaddr); + } + + char *next = NULL; + char *csets = strdup(coordinated_sets); + /* Set Level parsing*/ + char *set_details = strtok_r(csets, " ", &next); + + do { + tBTA_CSIP_CSET *cset = NULL; + uint8_t set_id = INVALID_SET_ID, size = 0, rank = 0; + uint16_t srvc_handle = 0; + bluetooth::Uuid uuid; + uint8_t sirk[SIRK_SIZE] = {}; + bool lock_support = false; + + char *part; + char *posn; + /* separating properties of set*/ + part = strtok_r(set_details, "~", &posn); + + while (part != NULL) + { + char *ptr = NULL; + /* Decode property type and its value*/ + char *prop_details = strtok_r(part, ":", &ptr); + + if (prop_details != NULL) { + char* prop_val = strtok_r(NULL, ":", &ptr); + if (prop_val) { + if (!strcmp(prop_details, "SET_ID")) { + set_id = (uint8_t)atoi(prop_val); + cset = bta_csip_get_or_create_cset(set_id, true); + if (!cset) LOG(INFO) << __func__ << " got cset empty"; + else LOG(INFO) << "valid " << +cset->set_id; + } else if (!strcmp(prop_details, "SIZE")) { + size = (uint8_t)atoi(prop_val); + } else if (!strcmp(prop_details, "SIRK")) { + hex_string_to_byte_arr(prop_val, sirk, SIRK_SIZE * 2); + } else if (!strcmp(prop_details, "INCLUDING_SRVC")) { + uuid = Uuid::FromString(prop_val); + } else if (!strcmp(prop_details, "LOCK_SUPPORT")) { + if (!strcmp(prop_val, "true")) lock_support = true; + } else if (!strcmp(prop_details, "RANK")) { + rank = (uint8_t)atoi(prop_val); + } else if (!strcmp(prop_details, "SRVC_HANDLE")) { + srvc_handle = (uint16_t)atoi(prop_val); + } + } + } + + part = strtok_r(NULL, "~", &posn); + } + + if (set_id < BTA_MAX_SUPPORTED_SETS) { + if (!cset) { + // Create new coordinated set insatnce and device to it + cset = bta_csip_get_or_create_cset(set_id, false); + cset->set_id = set_id; + cset->size = size; + cset->p_srvc_uuid = uuid; + cset->total_discovered = 1; + cset->set_members.push_back(bdaddr); + + // create coordinated set control block + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[set_id]; + cset_cb->set_id = set_id; + cset_cb->in_use = true; + memcpy(cset_cb->sirk, sirk, SIRK_SIZE); + if (rank != 0) { + cset_cb->ordered_members.insert({rank, bdaddr}); + } + + } else { + //LOG(INFO) << "Existing set = " << +cset->set_id; + cset->total_discovered++; + cset->set_members.push_back(bdaddr); + + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[set_id]; + if (rank != 0) { + cset_cb->ordered_members.insert({rank, bdaddr}); + } + } + + // assign service properties - set_id and bd_addr + tBTA_CSIS_SRVC_INFO *srvc = bta_csip_get_csis_service_cb(dev_cb); + if (srvc) { + srvc->set_id = set_id; + srvc->bd_addr = bdaddr; + srvc->size = size; + srvc->rank = rank; + srvc->service_handle = srvc_handle; + memcpy(srvc->sirk, sirk, SIRK_SIZE); + } + } + } while ((set_details = strtok_r(NULL, " ", &next)) != NULL); + + snode = config_section_next(snode); + } + + /*LOG(INFO) << "------------------DEBUG----------------------------"; + LOG(INFO) << "printing all loaded coordinated sets"; + for (int i = 0; i < (int)bta_csip_cb.csets.size(); i++) { + tBTA_CSIP_CSET set = bta_csip_cb.csets[i]; + LOG(INFO) << " Set ID = " << +set.set_id + << " Size = " << +set.size + << " total discovered = " << +set.total_discovered; + for (int j = 0; j < (int)set.set_members.size(); j++) { + LOG(INFO) << " Member (" << +(j+1) <<") = " << set.set_members[j].ToString(); + } + }*/ +} + +/************************************************************************************ + * + * Function bta_csip_preserve_cset + * + * Description function used to preserve coordinated set details to storage. + * + * Returns void + ************************************************************************************/ +void bta_csip_preserve_cset (tBTA_CSIS_SRVC_INFO* srvc) { + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(srvc->bd_addr); + + if (!p_cb) { + LOG(ERROR) << " Device cb record not found for " << srvc->bd_addr; + return; + } + + std::string& set_info = p_cb->set_info; + if (set_info.size() > 0) { + set_info += " "; + } + + set_info += "SET_ID:" + std::to_string(srvc->set_id); + + if (srvc->size_handle) { + set_info += "~SIZE:" + std::to_string(srvc->size); + } + + char sirk[SIRK_SIZE * 2 + 1] = {0}; + byte_arr_to_hex_string(srvc->sirk, sirk, SIRK_SIZE); + set_info += "~SIRK:" + std::string(sirk); + + if (!srvc->including_srvc_uuid.IsEmpty()) { + set_info += "~INCLUDING_SRVC:" + srvc->including_srvc_uuid.ToString(); + } + + if (srvc->lock_handle) { + set_info += "~LOCK_SUPPORT:" + std::string((srvc->lock_handle ? "true" : "false")); + } + + if (srvc->rank_handle) { + set_info += "~RANK:" + std::to_string(srvc->rank); + } + + set_info += "~SRVC_HANDLE:" + std::to_string(srvc->service_handle); + + LOG(INFO) << __func__ << " " << set_info; + + btif_config_set_str(p_cb->addr.ToString().c_str(), + "DGroup", set_info.c_str()); +} + +/************************************************************************************ + * + * Function bta_csip_get_salt + * + * Description function s1: Used to compute SALT. + * + * Returns Octet16 (cipher - SALT) + ************************************************************************************/ +Octet16 bta_csip_get_salt() { + Octet16 salt = {}; + Octet16 zero = {}; + uint8_t SIRKenc[] = {0x53, 0x49, 0x52, 0x4B, 0x65, 0x6E, 0x63}; + + salt = bta_csip_get_aes_cmac_result(zero, SIRKenc, 7); + + return salt; +} + +/************************************************************************************ + * + * Function bta_csip_compute_T + * + * Description First step of k1 function. Used to compute T from SALT and K. + * + * Returns Octet16 (cipher - T) + ************************************************************************************/ +Octet16 bta_csip_compute_T(Octet16 salt, Octet16 K) { + Octet16 T = {}; + + T = bta_csip_get_aes_cmac_result(salt, K); + + return T; +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description Second step of k1 function. Used to compute k1 from T and "csis". + * + * Returns Octet16 (cipher - k1) + ************************************************************************************/ +Octet16 bta_csip_compute_k1(Octet16 T) { + Octet16 k1 = {}; + uint8_t csis[] = {0x63,0x73,0x69,0x73}; + + k1 = bta_csip_get_aes_cmac_result(T, csis, 4); + + return k1; +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description sdf function. Used to compute SIRK from encrypted SIRK and k1. + * + * Returns Octet16 (SIRK) + ************************************************************************************/ +void bta_csip_get_decrypted_sirk(Octet16 k1, uint8_t *enc_sirk, uint8_t *sirk) { + for (int i = 0; i < 16; i++) { + sirk[i] = k1[i] ^ enc_sirk[i]; + } +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description Used to get aes-cmac result (for 16 byte input and output + * in LSB->MSB order) + * + * Returns Octet16 (cipher) + ************************************************************************************/ +Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const Octet16& message) { + Octet16 r_key, r_message, r_result; + + // reverse inputs as required by crypto_toolbox::aes_cmac + std::reverse_copy(key.begin(), key.end(), r_key.begin()); + std::reverse_copy(message.begin(), message.end(), r_message.begin()); + + Octet16 result = crypto_toolbox::aes_cmac(r_key, r_message.data(), r_message.size()); + + // reverse the result to get LSB->MSB order + std::reverse_copy(result.begin(), result.end(), r_result.begin()); + + return r_result; +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description Used to get aes-cmac result (for variable input and output + * in LSB->MSB order) + * + * Returns Octet16 (cipher) + ************************************************************************************/ +Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const uint8_t* input, + uint16_t length) { + Octet16 r_key, r_result; + + // reverse inputs as required by crypto_toolbox::aes_cmac + std::reverse_copy(key.begin(), key.end(), r_key.begin()); + uint8_t *input_buf = (uint8_t *)osi_malloc(length); + for (int i = 0; i < length; i++) { + input_buf[i] = input[length - 1 - i]; + } + + Octet16 result = crypto_toolbox::aes_cmac(r_key, input_buf, length); + + // reverse the result to get LSB->MSB order + std::reverse_copy(result.begin(), result.end(), r_result.begin()); + + return r_result; +} + +/************************************************************************************ + * + * Function byte_arr_to_hex_string + * + * Description function used to get hex representation in string format for byte[]. + * + * Returns void + ************************************************************************************/ +void byte_arr_to_hex_string(uint8_t* byte_arr, char* str, uint8_t len) { + int i; + LOG(INFO) << __func__ << " Convert byte array to hex format string"; + + for (i = 0; i < len; i++) + { + snprintf(str + (i * 2), (len * 2 + 1), "%02X", byte_arr[i]); + } +} + +/************************************************************************************ + * + * Function hex_string_to_byte_arr + * + * Description function used to get byte array from hex format string. + * + * Returns void + ************************************************************************************/ +void hex_string_to_byte_arr(char *str, uint8_t* byte_arr, uint8_t len) { + for (int length = 0; *str; str += 2, length++) + sscanf(str, "%02hhx", &byte_arr[length]); +} + +/************************************************************************************ + * + * Function is_key_empty + * + * Description function used to check if key is 0 intialized. + * + * Returns true, if all elements are 0. Otherwise, false. + ************************************************************************************/ +bool is_key_empty(Octet16& key) { + for (unsigned int i = 0; i < key.size(); i++) { + if (key[i] != 0) return false; + } + return true; +} diff --git a/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc b/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..4ff1ea40f1c08ad896c804cd87e1a2fdc83228e0 --- /dev/null +++ b/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc @@ -0,0 +1,1186 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#define LOG_TAG "bt_bta_dm_adv_audio" + +#include +#include +#include +#include + +#include "bt_common.h" +#include "bt_target.h" +#include "bt_types.h" +#include "bta_api.h" +#include "bta_dm_api.h" +#include "bta_dm_co.h" +#include "bta/dm/bta_dm_int.h" +#include "bta_csip_api.h" +#include "bta_sys.h" +#include "btif/include/btif_storage.h" +#include "btm_api.h" +#include "btm_int.h" +#include "btu.h" +#include "gap_api.h" /* For GAP_BleReadPeerPrefConnParams */ +#include "l2c_api.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" +#include "sdp_api.h" +#include "bta_sdp_api.h" +#include "stack/gatt/connection_manager.h" +#include "stack/include/gatt_api.h" +#include "utl.h" +#include "device/include/interop_config.h" +#include "device/include/profile_config.h" +#include "device/include/interop.h" +#include "stack/sdp/sdpint.h" +#include +#include "btif/include/btif_config.h" +#include "device/include/device_iot_config.h" +#include +#include +#include "bta_gatt_queue.h" +#include "bta_dm_adv_audio.h" +#include "btif/include/btif_dm_adv_audio.h" + +#if (GAP_INCLUDED == TRUE) +#include "gap_api.h" +#endif + +using bluetooth::Uuid; + +#define ADV_AUDIO_VOICE_ROLE_BIT 2 +#define ADV_AUDIO_MEDIA_ROLE_BIT 8 +#define CONN_LESS_MEDIA_SINK_ROLE_BIT 32 +#define CONN_LESS_ASSIST_ROLE_BIT 64 +#define CONN_LESS_DELEGATE_ROLE_BIT 128 +#define PACS_CT_SUPPORT_VALUE 2 +#define PACS_UMR_SUPPORT_VALUE 4 +#define PACS_CONVERSATIONAL_ROLE_VALUE 2 +#define PACS_MEDIA_ROLE_VALUE 4 + +#define BTA_DM_ADV_AUDIO_GATT_CLOSE_DELAY_TOUT 5000 + +Uuid UUID_SERVCLASS_WMCP = Uuid::FromString + ("2587db3c-ce70-4fc9-935f-777ab4188fd7"); + +std::vector uuid_srv_disc_search; +tBTA_LE_AUDIO_DEV_CB bta_le_audio_dev_cb; +tBTA_LEA_PAIRING_DB bta_lea_pairing_cb; +extern void bta_dm_proc_open_evt(tBTA_GATTC_OPEN* p_data); +bool is_adv_audio_unicast_supported(RawAddress rem_bda, int conn_id); + + +/*************************************************************************** + * + * Function bta_get_lea_ctrl_cb + * + * Description Gets the control block of LE audio device + * + * Parameters: tBTA_LE_AUDIO_DEV_INFO* + * + ****************************************************************************/ + +tBTA_LE_AUDIO_DEV_INFO* bta_get_lea_ctrl_cb(RawAddress peer_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = NULL; + p_lea_cb = &bta_le_audio_dev_cb.bta_lea_dev_info[0]; + + for (int i = 0; i < MAX_LEA_DEVICES ; i++) { + if (p_lea_cb[i].in_use && + (p_lea_cb[i].peer_address == peer_addr)) { + APPL_TRACE_DEBUG(" %s Control block Found for addr %s", + __func__, peer_addr.ToString().c_str()); + return &p_lea_cb[i]; + } + } + APPL_TRACE_DEBUG(" %s Control block Not Found for addr %s", + __func__, peer_addr.ToString().c_str()); + return NULL; +} + +/* Callback received when remote device Coordinated Sets SIRK is read */ +void bta_gap_gatt_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = + bta_get_lea_ctrl_cb(bta_le_audio_dev_cb.gatt_op_addr); + uint32_t role = 0; + uint8_t *p_val = value; + + STREAM_TO_ARRAY(&role, p_val, len); + + if (p_lea_cb) { + APPL_TRACE_DEBUG("%s Addr %s ", __func__, + p_lea_cb->peer_address.ToString().c_str()); + if (status == GATT_SUCCESS) { + if (p_lea_cb->t_role_handle == handle) { + LOG(INFO) << __func__ << " Role derived by T_ADV_AUDIO " + << +role; + if (role != 0) { + if (role & ADV_AUDIO_VOICE_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_VOICE)); + if (role & ADV_AUDIO_MEDIA_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_MEDIA_SINK)); + if (role & CONN_LESS_MEDIA_SINK_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_CONN_LESS_MEDIA_SINK)); + if (role & CONN_LESS_ASSIST_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_ASSIST)); + if (role & CONN_LESS_DELEGATE_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_DELEGATE)); + } + p_lea_cb->disc_progress--; + } else if(handle == p_lea_cb->pacs_char_handle) { + LOG(INFO) << __func__ << " derived by PACS " << +role; + if (role == 0) { + LOG(INFO) << __func__ << " Invalid Information "; + } else { + if (is_adv_audio_unicast_supported(bta_le_audio_dev_cb.gatt_op_addr, conn_id)) { + LOG(INFO) << __func__ << " ASCS Supported by the remote "; + if ((role & PACS_CONVERSATIONAL_ROLE_VALUE) == PACS_CT_SUPPORT_VALUE) + p_lea_cb->uuids.push_back(Uuid::From16Bit(UUID_SERVCLASS_PACS_CT_SUPPORT)); + if ((role & PACS_MEDIA_ROLE_VALUE) == PACS_UMR_SUPPORT_VALUE) + p_lea_cb->uuids.push_back(Uuid::From16Bit(UUID_SERVCLASS_PACS_UMR_SUPPORT)); + } + //TODO LEA_DBG Call API which will be provided by BAP + } + p_lea_cb->disc_progress--; + } else { + LOG(INFO) << __func__ << " Invalid Handle for LE AUDIO"; + } + } else { + p_lea_cb->disc_progress--; + LOG(INFO) << __func__ << " GATT READ FAILED" ; + } + + if (p_lea_cb->disc_progress <= 0) { + bta_dm_lea_disc_complete(p_lea_cb->peer_address); + } + } else { + LOG(INFO) << __func__ << " INVALID CONTROL BLOCK" ; + } + +} + + +/******************************************************************************* + * + * Function bta_get_adv_audio_role + * + * Description This API gets role for LE Audio Device after all services + * discovered + * + * Parameters: none + * + ******************************************************************************/ +void bta_get_adv_audio_role(RawAddress peer_address, uint16_t conn_id, + tGATT_STATUS status) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(peer_address); + + bta_le_audio_dev_cb.gatt_op_addr = peer_address; + + if (p_lea_cb == NULL) { + APPL_TRACE_ERROR(" %s Control block didnt find for peer address %s", __func__, + peer_address.ToString().c_str()); + return; + } + + // Fetch remote device gatt services from database + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + if (services) { + APPL_TRACE_DEBUG(" bta_get_adv_audio_role SIZE %d addr %s conn_id %d", + (*services).size(),bta_le_audio_dev_cb.gatt_op_addr.ToString().c_str(), + conn_id); + + // Search for CSIS service in the database + for (const gatt::Service& service : *services) { + APPL_TRACE_DEBUG("%s: SERVICES IN REMOTE DEVICE %s ", __func__, + service.uuid.ToString().c_str()) + if (is_le_audio_service(service.uuid)) { + size_t len = service.uuid.GetShortestRepresentationSize(); + uint16_t uuid_val = 0; + if (len == Uuid::kNumBytes16) { + uuid_val = service.uuid.As16Bit(); + } else if(len == Uuid::kNumBytes128) { + if (service.uuid == UUID_SERVCLASS_WMCP) { + APPL_TRACE_DEBUG("%s: WMCP Service UUId found", __func__); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + UUID_SERVCLASS_WMCP); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(UUID_SERVCLASS_WMCP); + } + } + } + + switch (uuid_val) { + case UUID_SERVCLASS_CSIS: + { + APPL_TRACE_DEBUG("%s:CSIS service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + + p_lea_cb->is_csip_support = true; + bta_dm_csis_disc_complete(bta_dm_search_cb.peer_bdaddr, false); + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid lock_uuid = charac.uuid; + if (lock_uuid.As16Bit() == UUID_SERVCLASS_CSIS_LOCK) { + APPL_TRACE_DEBUG("%s: CSIS rank found Uuid: %s ", __func__, + lock_uuid.ToString().c_str()); + if (p_lea_cb != NULL) { + Uuid csip_lock_uuid = Uuid::FromString("6AD8"); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + csip_lock_uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(csip_lock_uuid); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } + } + } + break; + case UUID_SERVCLASS_T_ADV_AUDIO: + { + if (!p_lea_cb->is_has_found) { + APPL_TRACE_DEBUG("%s: T_ADV_AUDIO service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + service.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(service.uuid); + } + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid role_uuid = charac.uuid; + if (role_uuid.As16Bit() == UUID_SERVCLASS_T_ADV_AUDIO_ROLE_CHAR) { + APPL_TRACE_DEBUG("%s:T_ADV_AUDIO ROLE CHAR found Uuid: %s ", __func__, + role_uuid.ToString().c_str()); + if (p_lea_cb != NULL) { + p_lea_cb->is_t_audio_srvc_found = true; + p_lea_cb->disc_progress++; + p_lea_cb->t_role_handle = charac.value_handle; + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } + } + if (p_lea_cb->t_role_handle) { + APPL_TRACE_DEBUG("%s t_role_handle %d", __func__, + p_lea_cb->t_role_handle); + BtaGattQueue::ReadCharacteristic(conn_id, p_lea_cb->t_role_handle, + bta_gap_gatt_read_cb, NULL); + } + } + } + break; + case UUID_SERVCLASS_HAS: + if (!p_lea_cb->is_t_audio_srvc_found) { + p_lea_cb->is_has_found = true; + APPL_TRACE_DEBUG("%s: HAS service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + service.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(service.uuid); + } + } + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_PACS: + { + if ((!p_lea_cb->pacs_char_handle) && + ((!p_lea_cb->is_t_audio_srvc_found))) { + APPL_TRACE_DEBUG("%s:PACS service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + service.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(service.uuid); + } + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid role_uuid = charac.uuid; + if (role_uuid.As16Bit() == UUID_SERVCLASS_SOURCE_CONTEXT) { + APPL_TRACE_DEBUG("%s: PACS Source context CHAR found Uuid: %s ", + __func__, role_uuid.ToString().c_str()); + if (p_lea_cb != NULL) { + p_lea_cb->disc_progress++; + p_lea_cb->pacs_char_handle = charac.value_handle; + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } + } + if (p_lea_cb->pacs_char_handle) { + BtaGattQueue::ReadCharacteristic(conn_id, p_lea_cb->pacs_char_handle, + bta_gap_gatt_read_cb, NULL); + } + } + } + break; + default: + APPL_TRACE_DEBUG(" Not a LE AUDIO SERVICE-- IGNORE %s ", + service.uuid.ToString().c_str()); + } + } + } + } + + if (p_lea_cb->disc_progress == 0) { + bta_dm_lea_disc_complete(peer_address); + } +} + +/***************************************************************************** + * + * Function bta_dm_csis_disc_complete + * + * Description This API updates csis discovery complete status + * + * Parameters: none + *****************************************************************************/ +void bta_dm_csis_disc_complete(RawAddress p_bd_addr, bool status) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s %s %d", __func__, p_bd_addr.ToString().c_str(), + status); + + if (p_lea_cb) { + p_lea_cb->csip_disc_progress = status; + } else { + RawAddress pseudo_addr = bta_get_pseudo_addr_with_id_addr(p_bd_addr); + if (pseudo_addr != RawAddress::kEmpty) { + p_lea_cb = bta_get_lea_ctrl_cb(pseudo_addr); + if (p_lea_cb) { + p_lea_cb->csip_disc_progress = status; + APPL_TRACE_DEBUG(" %s Pseudo addr disc_progress resetted", __func__); + } else { + APPL_TRACE_DEBUG(" %s No Control Block for pseudo addr", __func__); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } +} + +/***************************************************************************** + * + * Function bta_dm_lea_disc_complete + * + * Description This API sends the event to upper layer that LE audio + * gatt operations are complete. + * + * Parameters: none + * + ****************************************************************************/ +void bta_dm_lea_disc_complete(RawAddress p_bd_addr) { + tBTA_DM_SEARCH result; + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s %s", __func__, p_bd_addr.ToString().c_str()); + + if (p_lea_cb == NULL) { + RawAddress pseudo_addr = bta_get_pseudo_addr_with_id_addr(p_bd_addr); + p_lea_cb = bta_get_lea_ctrl_cb(pseudo_addr); + p_bd_addr = pseudo_addr; + } + + if (p_lea_cb) { + APPL_TRACE_DEBUG("csip_disc_progress %d", p_lea_cb->csip_disc_progress); + if ((p_lea_cb->disc_progress == 0) && + (p_lea_cb->csip_disc_progress)) { //Add CSIS check also + result.adv_audio_disc_cmpl.num_uuids = 0; + for (uint16_t i = 0; i < p_lea_cb->uuids.size(); i++) { + result.adv_audio_disc_cmpl.adv_audio_uuids[i] = p_lea_cb->uuids[i]; + result.adv_audio_disc_cmpl.num_uuids++; + } + + result.adv_audio_disc_cmpl.bd_addr = p_bd_addr; + APPL_TRACE_DEBUG("Sending Call back with no of uuids's" + "p_lea_cb->uuids.size() %d", p_lea_cb->uuids.size()); + bta_dm_search_cb.p_search_cback(BTA_DM_LE_AUDIO_SEARCH_CMPL_EVT, &result); + } else { + APPL_TRACE_DEBUG("%s Discovery in progress", __func__); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } +} + + +/***************************************************************************** + * + * Function bta_add_adv_audio_uuid + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ +void bta_add_adv_audio_uuid(RawAddress peer_address, + tBTA_GATT_ID srvc_uuid) { + auto itr = find(uuid_srv_disc_search.begin(), + uuid_srv_disc_search.end(), srvc_uuid.uuid); + + if(itr != uuid_srv_disc_search.end()) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(peer_address); + if (p_lea_cb != NULL) { + APPL_TRACE_DEBUG(" %s Control Block Found", __func__); + + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), srvc_uuid.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(srvc_uuid.uuid); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } +} + + + + +/******************************************************************************* + * + * Function bta_set_lea_ctrl_cb + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ + +tBTA_LE_AUDIO_DEV_INFO* bta_set_lea_ctrl_cb(RawAddress peer_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = NULL; + + p_lea_cb = bta_get_lea_ctrl_cb(peer_addr); + + if (p_lea_cb == NULL) { + APPL_TRACE_DEBUG("%s Control block create ", __func__); + + for (int i = 0; i < MAX_LEA_DEVICES ; i++) { + if (!bta_le_audio_dev_cb.bta_lea_dev_info[i].in_use) { + bta_le_audio_dev_cb.bta_lea_dev_info[i].peer_address = peer_addr; + bta_le_audio_dev_cb.bta_lea_dev_info[i].in_use = true; + bta_le_audio_dev_cb.bta_lea_dev_info[i].csip_disc_progress = true; + bta_le_audio_dev_cb.bta_lea_dev_info[i].is_csip_support = false; + bta_le_audio_dev_cb.bta_lea_dev_info[i].gatt_disc_progress = true; + bta_le_audio_dev_cb.num_lea_devices++; + return (&(bta_le_audio_dev_cb.bta_lea_dev_info[i])); + } + } + } else { + return p_lea_cb; + } + return NULL; +} + +/******************************************************************************* + * + * Function bta_dm_reset_adv_audio_dev_info + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ +void bta_dm_reset_adv_audio_dev_info(RawAddress p_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_addr); + + if (p_lea_cb != NULL) { + p_lea_cb->peer_address = RawAddress::kEmpty; + p_lea_cb->disc_progress = 0; + p_lea_cb->conn_id = 0; + p_lea_cb->transport = 0; + p_lea_cb->in_use = false; + p_lea_cb->t_role_handle = 0; + p_lea_cb->is_has_found = false; + p_lea_cb->is_t_audio_srvc_found = false; + p_lea_cb->pacs_char_handle = 0; + p_lea_cb->using_bredr_bonding = 0; + p_lea_cb->gatt_disc_progress = false; + p_lea_cb->uuids.clear(); + bta_le_audio_dev_cb.gatt_op_addr = RawAddress::kEmpty; + bta_le_audio_dev_cb.pending_peer_addr = RawAddress::kEmpty; + bta_le_audio_dev_cb.num_lea_devices--; + bta_le_audio_dev_cb.bond_progress = false; + APPL_TRACE_DEBUG("bta_dm_reset_adv_audio_dev_info %s transport %d ", + p_lea_cb->peer_address.ToString().c_str(), p_lea_cb->transport); + } +} + +/******************************************************************************* + * + * Function bta_dm_set_adv_audio_dev_info + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ +void bta_dm_set_adv_audio_dev_info(tBTA_GATTC_OPEN* p_data) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_set_lea_ctrl_cb(p_data->remote_bda); + + if (p_lea_cb != NULL) { + p_lea_cb->peer_address = p_data->remote_bda; + p_lea_cb->disc_progress = 0; + p_lea_cb->conn_id = p_data->conn_id; + p_lea_cb->transport = p_data->transport;//BTM_UseLeLink(p_data->remote_bda); + APPL_TRACE_DEBUG("bta_dm_set_adv_audio_dev_info %s transport %d ", + p_lea_cb->peer_address.ToString().c_str(), p_lea_cb->transport); + } +} + +/******************************************************************************* + * + * Function is_adv_audio_unicast_supported + * + * Description This function checks whether unicast support is there or not on + * remote side + * + * Parameters: + * + ******************************************************************************/ + +bool is_adv_audio_unicast_supported(RawAddress rem_bda, int conn_id) { + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + if (services) { + for (const gatt::Service& service : *services) { + uint16_t uuid_val = service.uuid.As16Bit(); + if (uuid_val == UUID_SERVCLASS_ASCS) + return true; + } + } + + return false; +} + +/******************************************************************************* + * + * Function is_adv_audio_group_supported + * + * Description This function checks whether csip support is there or not on + * remote side + * + * Parameters: + * + ******************************************************************************/ + +bool is_adv_audio_group_supported(RawAddress rem_bda, int conn_id) { + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + if (services) { + for (const gatt::Service& service : *services) { + if (is_le_audio_service(service.uuid)) { + uint16_t uuid_val = service.uuid.As16Bit(); + if (uuid_val == UUID_SERVCLASS_CSIS) + return true; + } + } + } + + return false; +} + +/******************************************************************************* + * + * Function bta_dm_lea_gattc_callback + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ + +void bta_dm_lea_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + APPL_TRACE_DEBUG("bta_dm_lea_gattc_callback event = %d", event); + + switch (event) { + case BTA_GATTC_OPEN_EVT: + if (p_data->status != GATT_SUCCESS) { + btif_dm_release_action_uuid(bta_le_audio_dev_cb.pending_peer_addr); + } else { + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_dm_set_adv_audio_dev_info(&p_data->open); + } + bta_dm_proc_open_evt(&p_data->open); + } + break; + + case BTA_GATTC_SEARCH_RES_EVT: + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_add_adv_audio_uuid(bta_le_audio_dev_cb.pending_peer_addr, + p_data->srvc_res.service_uuid); + } + break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_get_adv_audio_role(bta_le_audio_dev_cb.pending_peer_addr, + p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + if (is_adv_audio_group_supported(bta_le_audio_dev_cb.pending_peer_addr, + p_data->search_cmpl.conn_id)) { + RawAddress p_id_addr = + bta_get_rem_dev_id_addr(bta_le_audio_dev_cb.pending_peer_addr); + if (p_id_addr != RawAddress::kEmpty) { + BTA_CsipFindCsisInstance(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status, + p_id_addr); + } else { + BTA_CsipFindCsisInstance(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status, + bta_le_audio_dev_cb.pending_peer_addr); + } + } + } + break; + + case BTA_GATTC_CLOSE_EVT: + APPL_TRACE_DEBUG("BTA_GATTC_CLOSE_EVT reason = %d, data conn_id %d," + "search conn_id %d", p_data->close.reason, p_data->close.conn_id, + bta_dm_search_cb.conn_id); + + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_dm_reset_adv_audio_dev_info(bta_le_audio_dev_cb.pending_peer_addr); + } + break; + + default: + break; + } +} + +/****************************************************************************** + * + * Function bta_dm_adv_audio_gatt_conn + * + * Description This API opens the gatt conn after finding sdp record + * during BREDR Discovery + * + * Parameters: none + * + ******************************************************************************/ +void bta_dm_adv_audio_gatt_conn(RawAddress p_bd_addr) { + APPL_TRACE_DEBUG("bta_dm_adv_audio_gatt_conn "); + + bta_le_audio_dev_cb.pending_peer_addr = p_bd_addr; + + tBTA_LE_AUDIO_DEV_INFO *tmp_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + if (tmp_lea_cb && tmp_lea_cb->in_use) { + APPL_TRACE_DEBUG("bta_dm_adv_audio_gatt_conn Already exists %d", + tmp_lea_cb->conn_id); + return; + } + + BTA_GATTC_AppRegister(bta_dm_lea_gattc_callback, + base::Bind([](uint8_t client_id, uint8_t status) { + if (status == GATT_SUCCESS) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = + bta_set_lea_ctrl_cb(bta_le_audio_dev_cb.pending_peer_addr); + if (p_lea_cb) { + APPL_TRACE_DEBUG("bta_dm_adv_audio_gatt_conn Client Id: %d", + client_id); + p_lea_cb->gatt_if = client_id; + p_lea_cb->using_bredr_bonding = true; + BTA_GATTC_Open(client_id, bta_le_audio_dev_cb.pending_peer_addr, + true, GATT_TRANSPORT_LE, false); + } + } + }), false); + +} + +/****************************************************************************** + * + * Function bta_dm_adv_audio_close + * + * Description This API closes the gatt conn with was opened by dm layer + * for service discovery (or) opened after finding sdp record + * during BREDR Discovery + * + * Parameters: none + * + ******************************************************************************/ +void bta_dm_adv_audio_close(RawAddress p_bd_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s", __func__); + + if (p_lea_cb) { + APPL_TRACE_DEBUG("%s %d", __func__, p_lea_cb->gatt_if); + if (p_lea_cb->using_bredr_bonding) { + APPL_TRACE_DEBUG("%s closing LE conn est due to bredr bonding %d", __func__, + p_lea_cb->gatt_if); + BTA_GATTC_AppDeregister(p_lea_cb->gatt_if); + } else { + bta_sys_start_timer(bta_dm_search_cb.gatt_close_timer, + BTA_DM_ADV_AUDIO_GATT_CLOSE_DELAY_TOUT, + BTA_DM_DISC_CLOSE_TOUT_EVT, 0); + } + } +} + +/******************************************************************************* + * + * Function bta_get_lea_ctrl_cb + * + * Description This API returns pairing control block of LE AUDIO DEVICE + * + * Parameters: tBTA_DEV_PAIRING_CB + * + ******************************************************************************/ +tBTA_DEV_PAIRING_CB* bta_get_lea_pair_cb(RawAddress peer_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = &bta_lea_pairing_cb.bta_dev_pair_db[0]; + APPL_TRACE_DEBUG("%s %s ", __func__, peer_addr.ToString().c_str()); + + for (int i = 0; i < MAX_LEA_DEVICES; i++) { + if ((p_lea_pair_cb[i].in_use) && + (p_lea_pair_cb[i].p_addr == peer_addr)) { + APPL_TRACE_DEBUG("%s Found %s index i %d ", __func__, + p_lea_pair_cb[i].p_addr.ToString().c_str(), i); + return &p_lea_pair_cb[i]; + } + } + return NULL; +} + + + +/******************************************************************************* + * + * Function bta_set_lea_ctrl_cb + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ + +tBTA_DEV_PAIRING_CB* bta_set_lea_pair_cb(RawAddress peer_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + APPL_TRACE_DEBUG("bta_set_lea_ctrl_cb %s", peer_addr.ToString().c_str()); + + p_lea_pair_cb = bta_get_lea_pair_cb(peer_addr); + + if (p_lea_pair_cb == NULL) { + APPL_TRACE_DEBUG("bta_set_lea_ctrl_cb Control block create "); + + for (int i = 0; i < MAX_LEA_DEVICES ; i++) { + if (!bta_lea_pairing_cb.bta_dev_pair_db[i].in_use) { + bta_lea_pairing_cb.bta_dev_pair_db[i].p_addr = peer_addr; + bta_lea_pairing_cb.bta_dev_pair_db[i].in_use = true; + bta_lea_pairing_cb.is_pairing_progress = true; + bta_lea_pairing_cb.num_devices++; + return (&(bta_lea_pairing_cb.bta_dev_pair_db[i])); + } + } + } else { + return p_lea_pair_cb; + } + return NULL; +} + +/******************************************************************************* + * + * Function bta_dm_reset_adv_audio_dev_info + * + * Description This API resets all the pairing information related to le + * audio remote device. + * Parameters: none + * + ******************************************************************************/ +void bta_dm_reset_lea_pairing_info(RawAddress p_addr) { + + APPL_TRACE_DEBUG("%s Addr %s", __func__, p_addr.ToString().c_str()); + + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + bta_lea_pairing_cb.dev_addr_map.erase(p_addr); + } + + itr = bta_lea_pairing_cb.dev_rand_addr_map.find(p_addr); + if (itr != bta_lea_pairing_cb.dev_rand_addr_map.end()) { + bta_lea_pairing_cb.dev_rand_addr_map.erase(p_addr); + } + + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_addr); + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s RESETTING VALUES", __func__); + p_lea_pair_cb->in_use = false; + p_lea_pair_cb->is_dumo_device = false; + p_lea_pair_cb->is_le_pairing = false; + p_lea_pair_cb->dev_type = 0; + if (p_lea_pair_cb->p_id_addr != RawAddress::kEmpty) { + itr = bta_lea_pairing_cb.dev_addr_map.find(p_lea_pair_cb->p_id_addr); + APPL_TRACE_DEBUG("%s RESETTING Addr %s", __func__, + p_lea_pair_cb->p_id_addr.ToString().c_str()); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s Clearing INSIDE LEA ADDR DB MAP", + __func__); + bta_lea_pairing_cb.dev_addr_map.erase(p_lea_pair_cb->p_id_addr); + } + p_lea_pair_cb->p_id_addr = RawAddress::kEmpty; + p_lea_pair_cb->transport = 0; + p_lea_pair_cb->p_addr = RawAddress::kEmpty; + } + bta_lea_pairing_cb.is_pairing_progress = false; + bta_lea_pairing_cb.num_devices--; + bta_lea_pairing_cb.is_sdp_discover = true; + } else { + APPL_TRACE_DEBUG("%s INVALID CONTROL BLOCK", __func__); + } +} + +/***************************************************************************** + * + * Function bta_dm_ble_adv_audio_idaddr_map + * + * Description storing the identity address information in the device + * control block. It will used for DUMO devices + * + * Returns none + * + *****************************************************************************/ +void bta_dm_ble_adv_audio_idaddr_map(RawAddress p_bd_addr, + RawAddress p_id_addr) { + APPL_TRACE_DEBUG("%s p_bd_addr %s id_addr %s ", __func__, + p_bd_addr.ToString().c_str(), p_id_addr.ToString().c_str()); + if (is_remote_support_adv_audio(p_bd_addr)) { + bta_lea_pairing_cb.dev_addr_map[p_id_addr] = p_bd_addr; + bta_lea_pairing_cb.dev_rand_addr_map[p_bd_addr] = p_id_addr; + + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + if (p_lea_pair_cb) { + if (p_id_addr != p_bd_addr) { + APPL_TRACE_DEBUG("%s is_dumo_device %s", __func__, + p_id_addr.ToString().c_str()); + p_lea_pair_cb->p_id_addr = p_id_addr; + p_lea_pair_cb->is_dumo_device = true; + } + } + } +} + +bool bta_remote_dev_identity_addr_match(RawAddress p_addr) { + APPL_TRACE_DEBUG("%s ", __func__); + + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s Identity BD_ADDR %s", __func__, + p_addr.ToString().c_str()); + return true; + } + return false; +} + +bool bta_is_bredr_primary_transport(RawAddress p_bd_addr) { + + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s ", __func__); + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s Transport %d ", __func__, p_lea_pair_cb->transport); + if (p_lea_pair_cb->transport == BT_TRANSPORT_BR_EDR) { + return true; + } + } + + return false; +} + +bool bta_remote_device_is_dumo(RawAddress p_bd_addr) { + + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_bd_addr); + APPL_TRACE_DEBUG("%s Addr %s", __func__, p_bd_addr.ToString().c_str()); + + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s DUMO DEVICE Identity BD_ADDR %s", __func__, + p_bd_addr.ToString().c_str()); + return true; + } + + auto itr2 = bta_lea_pairing_cb.dev_rand_addr_map.find(p_bd_addr); + if (itr2 != bta_lea_pairing_cb.dev_rand_addr_map.end()) { + APPL_TRACE_DEBUG("%s Dumo addressed %s %s ", __func__, + itr2->first.ToString().c_str(), itr2->second.ToString().c_str()); + return true; + } + return false; +} + +RawAddress bta_get_rem_dev_id_addr(RawAddress p_bd_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + APPL_TRACE_DEBUG("%s ", __func__); + + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s %s", __func__, + p_lea_pair_cb->p_id_addr.ToString().c_str()); + return p_lea_pair_cb->p_id_addr; + } + return RawAddress::kEmpty; +} + +/***************************************************************************** + * + * Function bta_adv_audio_update_bond_db + * + * Description Updates pairing control block of the device and the bonding + * is initiated using LE transport or not. + * + * Returns void + * + *****************************************************************************/ +void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport) { + tBTA_DEV_PAIRING_CB *p_dev_pair_cb = bta_set_lea_pair_cb(p_bd_addr); + + APPL_TRACE_DEBUG("%s", __func__); + if (p_dev_pair_cb) { + APPL_TRACE_DEBUG("%s Addr %s Transport %d", __func__, + p_bd_addr.ToString().c_str(), transport); + p_dev_pair_cb->p_addr = p_bd_addr; + p_dev_pair_cb->transport = transport; + if (transport == BT_TRANSPORT_LE) { + if (is_remote_support_adv_audio(p_dev_pair_cb->p_addr)) + p_dev_pair_cb->is_le_pairing = true; + else + p_dev_pair_cb->is_le_pairing = false; + } else + p_dev_pair_cb->is_le_pairing = false; + } +} + +/***************************************************************************** + * + * Function is_le_audio_service + * + * Description It checks whether the given service is related to the LE + * Audio service or not. + * + * Returns true for LE Audio service which are registered. + false by default + * + *****************************************************************************/ +bool is_le_audio_service(Uuid uuid) { + + uint16_t uuid_val = 0; + bool status = false; + + size_t len = uuid.GetShortestRepresentationSize(); + if (len == Uuid::kNumBytes16) { + uuid_val = uuid.As16Bit(); + APPL_TRACE_DEBUG("is_le_audio_service : 0x%X 0x%X ", uuid.As16Bit(), uuid_val); + //TODO check the service contains any LE AUDIO service or not + switch (uuid_val) { + case UUID_SERVCLASS_CSIS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_BASS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_T_ADV_AUDIO: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_ASCS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_BAAS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_PACS: + { + auto itr = find(uuid_srv_disc_search.begin(), + uuid_srv_disc_search.end(), uuid); + if (itr != uuid_srv_disc_search.end()) + status = true; + } + break; + default: + APPL_TRACE_DEBUG("%s : Not a LEA service ", __func__); + } + } else if(len == Uuid::kNumBytes128) { + if (uuid == UUID_SERVCLASS_WMCP) { + APPL_TRACE_DEBUG("%s: WMCP Service UUId found", __func__); + auto itr = find(uuid_srv_disc_search.begin(), + uuid_srv_disc_search.end(), uuid); + if (itr != uuid_srv_disc_search.end()) + status = true; + } + } + + return status; +} + +/***************************************************************************** + * + * Function bta_is_adv_audio_valid_bdaddr + * + * Description This API is used for DUMO device. If the device contains + * two address (random and public), it checks for valid + * address. + * + * Returns 0 - for random address in dumo device + * 1 - for public address in dumo device + * + ****************************************************************************/ +int bta_is_adv_audio_valid_bdaddr(RawAddress p_bd_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s p_lea_pair_cb %s", __func__, + p_lea_pair_cb->p_addr.ToString().c_str()); + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_bd_addr); + if (itr == bta_lea_pairing_cb.dev_addr_map.end() && + (p_lea_pair_cb->is_dumo_device)) { + APPL_TRACE_DEBUG("%s Ignore BD_ADDR because of ID %s", __func__, + p_lea_pair_cb->p_id_addr.ToString().c_str()); + return 0; + } + } + return 1; +} + +/***************************************************************************** + * + * Function devclass2uint + * + * Description This API is to derive the class of device based of dev_class + * + * Returns uint32_t - class of device + * + ****************************************************************************/ +static uint32_t devclass2uint(DEV_CLASS dev_class) { + uint32_t cod = 0; + + if (dev_class != NULL) { + /* if COD is 0, irrespective of the device type set it to Unclassified + * device */ + cod = (dev_class[2]) | (dev_class[1] << 8) | (dev_class[0] << 16); + } + return cod; +} + +/***************************************************************************** + * + * Function bta_is_remote_support_lea + * + * Description This API is to check the remote device contains LEA service + * or not. It checks in Inquiry database initially. + * If the address is Public identity address then it will + * check in the pairing database of that remote device. + * + * Returns true - if remote device inquiry db contains LEA service + * + ****************************************************************************/ +bool bta_is_remote_support_lea(RawAddress p_addr) { + tBTM_INQ_INFO* p_inq_info; + + p_inq_info = BTM_InqDbRead(p_addr); + if (p_inq_info != NULL) { + uint32_t cod = devclass2uint(p_inq_info->results.dev_class); + BTIF_TRACE_DEBUG("%s cod is 0x%06x", __func__, cod); + if ((cod & MAJOR_LE_AUDIO_VENDOR_COD) + == MAJOR_LE_AUDIO_VENDOR_COD) { + return true; + } + } + + /* check the address is public identity address and its related to random + * address which supports to LEA then that Public ID address should return + * true. + */ + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + BTIF_TRACE_DEBUG("%s Idenity address mapping", __func__); + return true; + } + + return false; +} + +void bta_find_adv_audio_group_instance(uint16_t conn_id, tGATT_STATUS status, + RawAddress p_addr) { + RawAddress p_id_addr = + bta_get_rem_dev_id_addr(p_addr); + if (p_id_addr != RawAddress::kEmpty) { + BTA_CsipFindCsisInstance(conn_id, status, p_id_addr); + } else { + BTA_CsipFindCsisInstance(conn_id, status, p_addr); + } +} + +/******************************************************************************* + * + * Function is_gatt_srvc_disc_pending + * + * Description This function checks whether gatt_srvc_disc is processing + * or not + * + * Parameters: + * + ******************************************************************************/ +bool is_gatt_srvc_disc_pending(RawAddress rem_bda) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(rem_bda); + + APPL_TRACE_DEBUG("%s ", __func__); + if (p_lea_cb == NULL) { + return false; + } else { + APPL_TRACE_DEBUG("%s gatt_disc_progress %d ", __func__, + p_lea_cb->gatt_disc_progress); + return p_lea_cb->gatt_disc_progress; + } +} + +/****************************************************************************** + * + * Function bta_get_pseudo_addr_with_id_addr + * + * Description This function returns the mapping id_addr(if present) to + * pseudo addr + * + * Parameters: + * + *****************************************************************************/ +RawAddress bta_get_pseudo_addr_with_id_addr(RawAddress p_addr) { + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + + APPL_TRACE_DEBUG("%s p_addr %s ", __func__, p_addr.ToString().c_str()); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s addr is mapped to %s ", __func__, + itr->second.ToString().c_str()); + if (itr->second != RawAddress::kEmpty) { + return itr->second; + } + } + return p_addr; +} diff --git a/le_audio/system/bt/bta/include/bta_ascs_client_api.h b/le_audio/system/bt/bta/include/bta_ascs_client_api.h new file mode 100644 index 0000000000000000000000000000000000000000..5bcf48e5cb36e707e75f8d933daa28eb2c6496e8 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_ascs_client_api.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace bluetooth { +namespace bap { +namespace ascs { + +class AscsClient { + public: + virtual ~AscsClient() = default; + + static void Init(bluetooth::bap::ascs::AscsClientCallbacks* callbacks); + + static void CleanUp(uint16_t client_id); + + static AscsClient* Get(); + + virtual void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) = 0; + + virtual void Disconnect(uint16_t client_id, const RawAddress& address) = 0; + + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + + virtual void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) = 0; + + virtual void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs); + + virtual void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs); + + virtual void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops); + + virtual void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops); + + virtual void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops); + + virtual void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops); + + virtual void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops); + + virtual void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops); +}; + +} // namespace ascs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/include/bta_bap_uclient_api.h b/le_audio/system/bt/bta/include/bta_bap_uclient_api.h new file mode 100644 index 0000000000000000000000000000000000000000..33bd53bf141ccd52e399a7870bcc53b61e170348 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_bap_uclient_api.h @@ -0,0 +1,67 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#pragma once + +#include +#include "connected_iso_api.h" +#include +#include "bta_ascs_client_api.h" +#include "bta/bap/uclient_alarm.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using bluetooth::bap::pacs::PacsClientCallbacks; +using bluetooth::bap::ascs::AscsClientCallbacks; +using bluetooth::bap::cis::CisInterfaceCallbacks; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::alarm::BapAlarmCallbacks; + +class UcastClient : public PacsClientCallbacks, + public AscsClientCallbacks, + public CisInterfaceCallbacks, + public BapAlarmCallbacks { + public: + virtual ~UcastClient() = default; + + static void Initialize(UcastClientCallbacks* callbacks); + static void CleanUp(); + static UcastClient* Get(); + + // APIs exposed to upper layer + virtual void Connect(std::vector address, bool is_direct, + std::vector streams) = 0; + virtual void Disconnect(const RawAddress& address, + std::vector streams) = 0; + virtual void Start(const RawAddress& address, + std::vector streams) = 0; + virtual void Stop(const RawAddress& address, + std::vector streams) = 0; + virtual void Reconfigure(const RawAddress& address, + std::vector streams) = 0; + virtual void UpdateStream(const RawAddress& address, + std::vector update_streams) = 0; +}; + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/include/bta_cc_api.h b/le_audio/system/bt/bta/include/bta_cc_api.h new file mode 100644 index 0000000000000000000000000000000000000000..f7dbd6929c411f0b4609663113928203d432e4b5 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_cc_api.h @@ -0,0 +1,472 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright (C) 2003-2012 Broadcom Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include "bta_gatt_api.h" +#include +#include + +using bluetooth::Uuid; +using bluetooth::call_control::CallControllerCallbacks; +#define MAX_RESPONSE_DATA_LEN 255 +#define MAX_CCS_CONNECTION 5 +#define MAX_BEARER_NAME 255 +#define MAX_UCI_NAME 255 +#define MAX_URI_LENGTH 255 +#define MAX_BEARER_LIST_LEN 255 +#define MAX_FRIENDLY_NAME_LEN 255 +//Atmost there could be only two Indicies (for JOIN) +//Keeping this as Internal max as SPec don't define any upper limit on this +#define MAX_NUM_INDICIES 10 + +#define INBAND_RINGTONE_FEATURE_BIT 0x01 +#define SILENT_MODE_FEATURE_BIT 0x02 + +typedef enum { + CCS_NONE_EVENT = 120, + CCS_INIT_EVENT, + CCS_CLEANUP_EVENT, + CCS_CALL_STATE_UPDATE, + CCS_BEARER_NAME_UPDATE, + CCS_BEARER_UCI_UPDATE, + CCS_BEARER_URI_SCHEMES_SUPPORTED, + CCS_UPDATE, + CCS_OPT_OPCODES, + CCS_BEARER_CURRENT_CALL_LIST_UPDATE, + CCS_BEARER_SIGNAL_STRENGTH_UPDATE, + CCS_SIGNAL_STRENGTH_REPORT_INTERVAL, + CCS_STATUS_FLAGS_UPDATE, + CCS_INCOMING_CALL_UPDATE, + CCS_INCOMING_TARGET_URI_UPDATE, + CCS_TERMINATION_REASON_UPDATE, + CCS_BEARER_TECHNOLOGY_UPDATE, + CCS_CCID_UPDATE, + CCS_ACTIVE_DEVICE_UPDATE, + CCS_CALL_CONTROL_RESPONSE, + //events to handle in CCS state machine + CCS_NOTIFY_ALL, + CCS_WRITE_RSP, + CCS_READ_RSP, + CCS_DESCRIPTOR_WRITE_RSP, + CCS_DESCRIPTOR_READ_RSP, + CCS_CONNECTION, + CCS_DISCONNECTION, + CCS_CONNECTION_UPDATE, + CCS_CONGESTION_UPDATE, + CCS_PHY_UPDATE, + CCS_MTU_UPDATE, + CCS_SET_ACTIVE_DEVICE, + CCS_CONNECTION_CLOSE_EVENT, + CCS_BOND_STATE_CHANGE_EVENT, +}cc_event_t; + +typedef enum { + CCS_STATUS_SUCCESS = 0x00, + CCS_OPCODE_NOT_SUPPORTED, + CCS_OPCODE_UNSUCCESSFUL, + CCS_INVALID_INDEX, + CCS_STATE_MISMATCH, + CCS_LACK_OF_RESOURCES, + CCS_INVALID_OUTGOING_URI, + CCS_CALL_STATE_INACTIVE, +}cc_error_t; + +typedef enum { + CCS_DISCONNECTED = 0x00, + CCS_CONNECTED, + CCS_MAX_DEVICE_STATE +} call_connect_state_t; + +typedef enum { + CALL_ACCEPT = 0x00, + CALL_TERMINATE, + CALL_LOCAL_HOLD, + CALL_LOCAL_RETRIEVE, + CALL_ORIGINATE, + CALL_JOIN, + } cc_opcode_t; + + typedef enum { + CC_TERM_INVALID_ORIG_URI = 0x00, + CC_TERM_FAILED, + CC_TERM_END_FROM_REMOTE, + CC_TERM_END_FROM_SERVER, + CC_TERM_LINE_BUSY, + CC_TERM_NW_CONGESTION, + CC_TERM_END_FROM_CLIENT, + CC_TERM_NO_SERVICE, + CC_TERM_NO_ANSWER, + } cc_term_reason_t; + + typedef enum { + CCS_STATE_INCOMING = 0x00, + CCS_STATE_DIALING, + CCS_STATE_ALERTING, + CCS_STATE_ACTIVE, + CCS_STATE_LOCAL_HELD, + CCS_STATE_REMOTELY_HELD, + CCS_STATE_LOCAL_REMOTE_HELD, + CCS_STATE_DISCONNECTED, + } cc_state_t; + + //connection state machine + bool DeviceStateConnectionHandler(uint32_t event, void* param); + bool DeviceStateDisConnectingHandler(uint32_t event, void* param); + bool DeviceStateDisconnectedHandler(uint32_t event, void* param); + +typedef struct { + int server_if; + Uuid ccs_service_uuid; + Uuid bearer_provider_name_uuid; + Uuid call_control_point_uuid; + Uuid call_control_point_opcode_supported_uuid; + Uuid bearer_uci_uuid; + Uuid bearer_technology_uuid; + Uuid bearer_uri_schemes_supported_uuid; + Uuid bearer_signal_strength_uuid; + Uuid bearer_signal_strength_report_interval_uuid; + Uuid bearer_list_currentcalls_uuid; + Uuid incoming_call_target_beareruri_uuid; + Uuid call_status_flags_uuid; + Uuid call_state_uuid; + Uuid gtbs_ccid_uuid; + Uuid call_termination_reason_uuid; + Uuid incoming_call_uuid; + Uuid call_friendly_name_uuid; + //handle for characteristics + uint16_t call_state_handle; + uint16_t bearer_provider_name_handle; + uint16_t call_control_point_opcode_supported_handle; + uint16_t call_control_point_handle; + uint16_t bearer_uci_handle; + uint16_t bearer_technology_handle; + uint16_t bearer_uri_schemes_supported_handle; + uint16_t bearer_signal_strength_handle; + uint16_t bearer_signal_strength_report_interval_handle; + uint16_t bearer_list_currentcalls_handle; + uint16_t incoming_call_target_beareruri_handle; + uint16_t call_status_flags_handle; + uint16_t call_termination_reason_handle; + uint16_t incoming_call_handle; + uint16_t call_friendly_name_handle; + uint16_t ccid_handle; + uint16_t call_state_desc; + uint16_t bearer_provider_name_desc; + uint16_t call_control_point_opcode_supported_desc; + uint16_t call_control_point_desc; + uint16_t bearer_uci_desc; + uint16_t bearer_technology_desc; + uint16_t bearer_uri_schemes_supported_desc; + uint16_t bearer_signal_strength_desc; + uint16_t bearer_signal_strength_report_interval_desc; + uint16_t bearer_list_currentcalls_desc; + uint16_t incoming_call_target_bearerURI_desc; + uint16_t call_status_flags_desc; + uint16_t call_termination_reason_desc; + uint16_t incoming_call_desc; + uint16_t call_friendly_name_desc; + uint16_t ccid_desc; +}CcsControlServiceInfo_t; + +typedef struct { + call_connect_state_t state; + uint16_t call_state_notify; + uint16_t bearer_provider_name_notify; + uint16_t call_control_point_notify; + uint16_t call_control_point_opcode_supported_notify; + uint16_t bearer_technology_changed_notify; + uint16_t bearer_uci_notify; + uint16_t bearer_uri_schemes_supported_notify; + uint16_t bearer_current_calls_list_notify; + uint16_t bearer_signal_strength_notify; + uint16_t bearer_signal_strength_report_interval_notify; + uint16_t incoming_call_state_notify; + uint16_t incoming_call_target_URI_notify; + uint16_t status_flags_notify; + uint16_t call_termination_reason_notify; + uint16_t call_friendly_name_notify; + uint8_t signal_strength_report_interval; + alarm_t* signal_strength_reporting_timer; + bool congested; + int conn_id; + int trans_id; + int timeout; + int latency; + int interval; + int rx_phy; + int tx_phy; + int mtu; + + RawAddress peer_bda; + bool (*DeviceStateHandlerPointer[2])(uint32_t event, void* param); +}CallControllerDeviceList; + +typedef struct { + std::vector address; + int set_id; +}CallActiveDevice; + +typedef struct { + uint8_t index; + uint8_t state; + uint8_t flags; +}tCCS_CALL_STATE; + +typedef struct { + uint8_t index; + uint8_t incoming_target_uri[MAX_URI_LENGTH]; +}tCCS_INCOMING_CALL_URI; + +typedef struct { + uint8_t index; + uint8_t incoming_uri[MAX_URI_LENGTH]; +}tCCS_INCOMING_CALL; + +typedef struct { + uint8_t operation; + uint8_t index[MAX_NUM_INDICIES]; + uint8_t supported_flags; + char* uri; + uint32_t ccid; +}tCCS_CALL_CONTROL_POINT; + +typedef struct { + uint8_t opcode; + uint8_t index; + uint8_t response_status; + RawAddress remote_address; +}tCCS_CALL_CONTROL_RESPONSE; + +typedef struct { + uint16_t supported_flags; +}tCCS_STATUS_FLAGS; + +typedef struct { + uint16_t supp_opcode; +}tCCS_SUPP_OPTIONAL_OPCODES; + + +typedef struct { + uint8_t index; + uint8_t reason; +}tCCS_TERM_REASON; + +typedef struct { + uint8_t index; + uint8_t name[MAX_FRIENDLY_NAME_LEN]; +}tCCS_FRIENDLY_NAME; + +typedef struct { + uint32_t ccid; +}tCCS_CONTENT_CONTROL_ID; + +typedef struct { + RawAddress addr; +}tCCS_CONNECTION_CLOSE; + +typedef struct { + uint8_t list_length; + uint8_t call_index; + uint8_t call_state; + uint8_t call_flags; + uint8_t call_uri[MAX_URI_LENGTH]; + }tCCS_BEARER_LIST_CURRENT_CALLS; + +typedef struct { + uint8_t name[MAX_BEARER_NAME]; + uint8_t uci[MAX_UCI_NAME]; + uint8_t length; + uint8_t technology_type; + uint8_t signal; + uint8_t signal_report_interval; + int bearer_list_len; + uint8_t bearer_schemes_list[MAX_BEARER_LIST_LEN]; +}tCCS_BEARER_PROVIDER_INFO; + +typedef struct { + bool status; +}tCCS_BEARER_URI_SCHEMES; + +//Union ops +struct tCCS_CHAR_DESC_WRITE { + tCCS_CHAR_DESC_WRITE() {}; + ~tCCS_CHAR_DESC_WRITE() {}; + std::vector value; + uint8_t status; + uint16_t notification; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; + bool need_rsp; + bool prep_rsp; + //is to send notification +}; + +struct tCCS_CHAR_DESC_READ { + tCCS_CHAR_DESC_READ() {}; + ~tCCS_CHAR_DESC_READ() {}; + uint8_t status; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; +}; + +struct tCCS_CHAR_GATT_READ { + tCCS_CHAR_GATT_READ() {}; + ~tCCS_CHAR_GATT_READ() {}; + uint8_t status; + uint32_t trans_id; + uint32_t char_handle; +}; + +struct tCCS_CHAR_WRITE { + tCCS_CHAR_WRITE() {}; + ~tCCS_CHAR_WRITE() {}; + uint8_t status; + bool need_rsp; + bool prep_rsp; + uint16_t offset; + uint16_t trans_id; + uint32_t char_handle; + int len; + std::vector value; + uint8_t *data; +}; + +struct tCCS_CONNECTION { + uint8_t status; + CallControllerDeviceList remoteDevice; +}; + +struct tCCS_CONN_UPDATE { + tCCS_CONN_UPDATE() {}; + ~tCCS_CONN_UPDATE() {}; + uint8_t status; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_DISCONNECTION { + tCCS_DISCONNECTION() {}; + ~tCCS_DISCONNECTION() {}; + uint8_t status; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_CONGESTION { + tCCS_CONGESTION() {}; + ~tCCS_CONGESTION() {}; + bool congested; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_PHY{ + tCCS_PHY(); + ~tCCS_PHY(); + uint8_t status; + uint8_t tx_phy; + uint8_t rx_phy; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_MTU { + tCCS_MTU() {}; + ~tCCS_MTU() {}; + uint8_t status; + uint16_t mtu; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_SET_ACTIVE_DEVICE { + tCCS_SET_ACTIVE_DEVICE() {}; + ~tCCS_SET_ACTIVE_DEVICE() {}; + RawAddress address; + uint16_t set_id; +}; + +struct tCALL_CONTROL_UPDATE { + tCALL_CONTROL_UPDATE() {}; + ~tCALL_CONTROL_UPDATE() {}; + std::vector data; +}; + +union CALL_CONTROL_OPERATION{ + CALL_CONTROL_OPERATION() : CallControllerOp() { + }; + ~CALL_CONTROL_OPERATION() {}; + tCALL_CONTROL_UPDATE CallControllerOp; + tCCS_SET_ACTIVE_DEVICE SetActiveDeviceOp; + tCCS_CHAR_DESC_WRITE WriteDescOp; + tCCS_CHAR_DESC_READ ReadDescOp; + tCCS_CHAR_WRITE WriteOp; + tCCS_CHAR_GATT_READ ReadOp; + tCCS_CONNECTION ConnectionOp; + tCCS_CONN_UPDATE ConnectionUpdateOp; + tCCS_DISCONNECTION DisconnectionOp; + tCCS_CONGESTION CongestionOp; + tCCS_MTU MtuOp; + tCCS_PHY PhyOp; +}; + +typedef union CALL_CONTROL_OPERATION tCCS_OPERATION; + +struct tcc_resp_t { + tcc_resp_t() {}; + ~tcc_resp_t() {}; + uint32_t event = 0; + uint16_t handle = 0; + uint16_t status = 0; + bool force = false; + CallControllerDeviceList *remoteDevice = nullptr; + tGATTS_RSP rsp_value; + tCCS_OPERATION oper; + }; + +class CallController { + + public: + virtual ~CallController() = default; + static void Initialize(bluetooth::call_control::CallControllerCallbacks* callbacks, + Uuid app_id, int max_ccs_clients, bool inband_ringing_enabled); + static void CleanUp(); + static CallController* Get(); + static bool IsCcServiceRunnig(); + + virtual void CallState(int len, std::vector value) = 0; + virtual void BearerInfoName(uint8_t* bearer_name) = 0; + virtual void UpdateBearerTechnology(int tech_type) = 0; + virtual void UpdateSupportedBearerList(uint8_t* list) = 0; + virtual void UpdateIncomingCallTargetUri(int index, uint8_t* target_uri) = 0; + virtual void UpdateIncomingCall(int index, uint8_t* Uri) = 0; + virtual void UpdateBearerSignalStrength(int signal) = 0; + virtual void UpdateStatusFlags(uint8_t status_flag) = 0; + virtual void CallControlOptionalOpSupported(int feature) = 0; + virtual void CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address)= 0; + virtual void SetActiveDevice(const RawAddress& address, int setId) = 0; + virtual void ContentControlId(uint32_t ccid) = 0; + virtual void Disconnect(const RawAddress& bd_add) = 0; +}; + +void HandleCcsEvent(uint32_t event, void* param); +bool CCSHandler(uint32_t event, void* param); +void CcpCongestionUpdate(tcc_resp_t * p_data); diff --git a/le_audio/system/bt/bta/include/bta_csip_api.h b/le_audio/system/bt/bta/include/bta_csip_api.h new file mode 100644 index 0000000000000000000000000000000000000000..4efb422b4943c9983961b50f1258c26a83479d7b --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_csip_api.h @@ -0,0 +1,396 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +/****************************************************************************** + * + * This is the public interface file to provide CSIP API's. + * + ******************************************************************************/ + +#ifndef BTA_CSIP_API_H +#define BTA_CSIP_API_H + +//#include "bta_api.h" +#include +#include + +#include "bta_gatt_api.h" //temp + +#include + +#define SIRK_SIZE 16 // SIRK Size +#define UNLOCK_VALUE 0x01 // UNLOCK Value +#define LOCK_VALUE 0x02 // LOCK Value +#define ENCRYPTED_SIRK 0x00 // Encrypted SIRK Type +#define PLAINTEXT_SIRK 0x01 // Plain Text SIRK +#define INVALID_SET_ID 0x10 // Invalid set id + +/* status for applications for LOCK Status changed callback*/ +enum { + LOCK_RELEASED, // (LOCK Released successfully) + LOCK_RELEASED_TIMEOUT, // (LOCK Released by timeout) + ALL_LOCKS_ACQUIRED, // (LOCK Acquired for all requested set members) + SOME_LOCKS_ACQUIRED_REASON_TIMEOUT, // (Request timeout for some set members) + SOME_LOCKS_ACQUIRED_REASON_DISC, // (Some of the set members were disconnected) + LOCK_DENIED, // (Denied by one of the set members) + INVALID_REQUEST_PARAMS, // (Upper layer provided invalid parameters) + LOCK_RELEASE_NOT_ALLOWED, // (Response from remote (PTS)) + INVALID_VALUE, // (Response from remote (PTS)) +}; + +/* LOCK Request Error Codes from set members */ +#define CSIP_LOCK_DENIED 0x80 +#define CSIP_LOCK_RELEASE_NOT_ALLOWED 0x81 +#define CSIP_INVALID_LOCK_VALUE 0x82 +#define CSIP_LOCK_ALREADY_GRANTED 0x84 + +/* Events when CSIP operations are completed */ +#define BTA_CSIP_NEW_SET_FOUND_EVT 1 +#define BTA_CSIP_SET_MEMBER_FOUND_EVT 2 +#define BTA_CSIP_CONN_STATE_CHG_EVT 3 +#define BTA_CSIP_LOCK_STATUS_CHANGED_EVT 4 +#define BTA_CSIP_LOCK_AVAILABLE_EVT 5 +#define BTA_CSIP_SET_SIZE_CHANGED 6 +#define BTA_CSIP_SET_SIRK_CHANGED 7 + +/* CSIP operation completed event*/ +typedef uint8_t tBTA_CSIP_EVT; + +enum { + BTA_CSIP_SUCCESS, + BTA_CSIP_FAILURE, +}; + +/* CSIP Operation Status*/ +typedef uint8_t tBTA_CSIP_STATUS; + +/* CSIP GATT Connection States (to be notified to upper layer)*/ +/* Mapping to BluetoothProfile Connection States*/ +enum { + BTA_CSIP_DISCONNECTED, + BTA_CSIP_CONNECTED = 0x02, +}; + + /* CSIP device GATT Connection state */ +typedef uint8_t tBTA_CSIP_CONN_STATE; + +enum { + BTA_CSIP_CONN_ESTABLISHED = 0x40, // reason values to be decided + BTA_CSIP_CONN_ESTABLISHMENT_FAILED, + BTA_CSIP_APP_ALREADY_CONNECTED, + BTA_CSIP_APP_ALREADY_DISCONNECTED, + BTA_CSIP_APP_DISCONNECTED, + BTA_CSIP_DISCONNECT_WITHOUT_CONNECT, + BTA_CSIP_COORDINATED_SET_NOT_SUPPORTED, +}; + +/* CSIP device GATT Connection state */ +typedef uint8_t tBTA_CSIP_CONN_STATUS; + +/* Params in callback to requesting app when lock status has been changed */ +typedef struct { + uint8_t app_id; + uint8_t set_id; + uint8_t value = UNLOCK_VALUE; + uint8_t status; + std::vector addr; +} tBTA_LOCK_STATUS_CHANGED; + +/* Params in callback to registered app when coordinated set member is discovered */ +typedef struct { + uint8_t set_id; + bluetooth::Uuid uuid; + RawAddress addr; +} tBTA_SET_MEMBER_FOUND; + +/* Params in callback to registered app when discovery for coordinated set is completed */ +/*TODO: not required, to be removed */ +typedef struct { + uint8_t set_id; + bluetooth::Uuid uuid; + std::vector addr; +} tBTA_SET_DISC_CMPL; + +/* params in callback to app when lock is available on earlier denied member*/ +typedef struct { + uint8_t app_id; + uint8_t set_id; + RawAddress addr; +} tBTA_LOCK_AVAILABLE; + +/* params in callback to app space when new set is found*/ +typedef struct { + uint8_t set_id; + uint8_t sirk[SIRK_SIZE]; + uint8_t size; + bool lock_support; + RawAddress addr; + bluetooth::Uuid including_srvc_uuid; +} tBTA_CSIP_NEW_SET_FOUND; + +/* params in callback to app when set size is changed */ +typedef struct { + uint8_t set_id; + uint8_t size; + RawAddress addr; +} tBTA_CSIP_SET_SIZE_CHANGED; + +/* params in callback to app when set sirk is changed */ +typedef struct { + uint8_t set_id; + uint8_t* sirk; + RawAddress addr; +} tBTA_CSIP_SET_SIRK_CHANGED; + +/* params in callback to app when connection state has been changed */ +typedef struct { + uint8_t app_id; + RawAddress addr; + tBTA_CSIP_CONN_STATE state; + tBTA_CSIP_CONN_STATUS status; +} tBTA_CSIP_CONN_STATE_CHANGED; + +/* callbacks params on completion of CSIP operations */ +typedef union { + tBTA_LOCK_STATUS_CHANGED lock_status_param; + tBTA_CSIP_CONN_STATE_CHANGED conn_params; + tBTA_SET_MEMBER_FOUND set_member_param; + tBTA_SET_DISC_CMPL set_disc_cmpl_param; + tBTA_LOCK_AVAILABLE lock_available_param; + tBTA_CSIP_NEW_SET_FOUND new_set_params; + tBTA_CSIP_CONN_STATE_CHANGED conn_chg_params; + tBTA_CSIP_SET_SIZE_CHANGED size_chg_params; + tBTA_CSIP_SET_SIRK_CHANGED sirk_chg_params; +} tBTA_CSIP_DATA; + +/* CSIP callbacks to be given to upper layers*/ + +/* Callback given when one of the operation mentioned in */ +typedef void (tBTA_CSIP_CBACK) (tBTA_CSIP_EVT event, tBTA_CSIP_DATA* p_data); + +/* Callback when application is registered with CSIP */ +typedef void (tBTA_CSIP_CLT_REG_CB) (tBTA_CSIP_STATUS status, uint8_t app_id); + +/* parameters used for api BTA_CsipSetLockValue() */ +typedef struct { + uint8_t app_id; + uint8_t set_id; + uint8_t lock_value; + std::vector members_addr; +} tBTA_SET_LOCK_PARAMS; + +/* Coordinated set details */ +typedef struct { + uint8_t set_id; + uint8_t size; + uint8_t total_discovered; + bool lock_support; + std::vector set_members; + bluetooth::Uuid p_srvc_uuid; +} tBTA_CSIP_CSET; + +using BtaCsipAppRegisteredCb = + base::Callback; + +/********************************************************************************* + * + * Function BTA_RegisterCsipApp + * + * Description This function is called to register application or module to + * to register with CSIP for using CSIP functionalities. + * + * Parameters p_csip_cb: callback to be received in registering app when + * required CSIP operation is completed. + * reg_cb : callback when app/module is registered with CSIP. + * + * Returns None + * + *********************************************************************************/ +extern void BTA_RegisterCsipApp(tBTA_CSIP_CBACK* p_csip_cb, + BtaCsipAppRegisteredCb reg_cb); + +/********************************************************************************* + * + * Function BTA_UnregisterCsipApp + * + * Description This function is called to register application or module to + * to register with CSIP for using CSIP functionalities. + * + * Parameters app_id: Identifier of the app that needs to be unregistered. + * + * Returns None + * + *********************************************************************************/ +extern void BTA_UnregisterCsipApp(uint8_t app_id); + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters lock_param: parameters to acquire or release lock. + * (tBTA_SET_LOCK_PARAMS). + * + * Returns None + * + *********************************************************************************/ +extern void BTA_CsipSetLockValue(tBTA_SET_LOCK_PARAMS lock_param); + +/********************************************************************************* + * + * Function BTA_CsipGetCoordinatedSet + * + * Description This function is called to fetch details of the coordinated set. + * + * Parameters set_id: identifier of the coordinated set whose details are + * required to be fetched. + * + * Returns tBTA_CSIP_CSET (containing details of coordinated set). + * + *********************************************************************************/ +extern tBTA_CSIP_CSET BTA_CsipGetCoordinatedSet(uint8_t set_id); + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters None. + * + * Returns vector: (all discovered coordinated set) + * + *********************************************************************************/ +extern std::vector BTA_CsipGetDiscoveredSets(); + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + * Note This shouldnt be used by registered module. CSIP Profile + * internally manages GATT Connection. + * + *********************************************************************************/ +extern void BTA_CsipConnect (uint8_t app_id, const RawAddress& bd_addr); + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + * Note This shouldnt be used by registered module. CSIP Profile + * internally manages GATT Connection. + * + *********************************************************************************/ +extern void BTA_CsipDisconnect (uint8_t app_id, const RawAddress& bd_addr); + + +/********************************************************************************* + * + * Function BTA_CsipEnable + * + * Description This function is invoked to initialize CSIP in BTA layer. + * + * Parameters p_cback: callbacks registered with btif_csip module. + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +extern void BTA_CsipEnable(tBTA_CSIP_CBACK *p_cback); + +/********************************************************************************* + * + * Function BTA_CsipEnable + * + * Description This function is invoked to deinitialize CSIP in BTA layer. + * + * Parameters None. + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +extern void BTA_CsipDisable(); + +/********************************************************************************* + * + * Function BTA_CsipFindCsisInstance + * + * Description This function is invoked to find presence of CSIS service on + * remote device and start coordinated set discovery. + * + * Parameters conn_id: GATT Connection ID used for getting remote services + * status : Status of the discovery procedure + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +extern void BTA_CsipFindCsisInstance(uint16_t conn_id, tGATT_STATUS status, + RawAddress& bd_addr); + +/********************************************************************************* + * + * Function BTA_CsipRemoveUnpairedSetMember + * + * Description This function is called when a given set member is unpaired. + * + * Parameters addr: BD Address of the set member. + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +void BTA_CsipRemoveUnpairedSetMember(RawAddress addr); + +/********************************************************************************* + * + * Function BTA_CsipGetDeviceSetId + * + * Description This API is used to get set id of the remote device. + * + * Parameters addr: BD Address of the set member. + * uuid: UUID of the service which includes CSIS service. + * + * Returns None. + * + *********************************************************************************/ +uint8_t BTA_CsipGetDeviceSetId(RawAddress addr, bluetooth::Uuid uuid); + + +#endif /* BTA_CSIP_API_H */ diff --git a/le_audio/system/bt/bta/include/bta_dm_adv_audio.h b/le_audio/system/bt/bta/include/bta_dm_adv_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..d7fe588ae5465657860b56112ad01cc401d2e049 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_dm_adv_audio.h @@ -0,0 +1,152 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#ifndef BTA_DM_ADV_AUDIO_H +#define BTA_DM_ADV_AUDIO_H + +#include "stack/include/bt_types.h" +#include "bta/dm/bta_dm_int.h" +#include +#include +#include "bt_target.h" +#include "bta_sys.h" + +#include "bta_gatt_api.h" + +#define UUID_SERVCLASS_CSIS 0x1846 /* Coordinated Set Identification Service */ +#define UUID_SERVCLASS_PACS 0x1850 /* LE AUDIO PACS */ +#define UUID_SERVCLASS_ASCS 0x184E /* LE AUDIO ASCS */ +#define UUID_SERVCLASS_BASS 0x184F /* LE AUDIO BASS */ +#define UUID_SERVCLASS_BAAS 0x1851 /* LE AUDIO BAAS */ +#define UUID_SERVCLASS_BRASS 0x1852 /* LE AUDIO BRASS */ +#define UUID_SERVCLASS_T_ADV_AUDIO 0x1FA0 /* LE AUDIO T_ADV_AUDIO */ +#define UUID_SERVCLASS_CSIS_LOCK 0x2B86 /* LE AUDIO CSIS LOCK */ +#define UUID_SERVCLASS_T_ADV_AUDIO_ROLE_CHAR 0xFE00 /*LE AUDIO CSIS LOCK */ +#define UUID_SERVCLASS_T_ADV_AUDIO_MEDIA_SINK 0x6AD0 +#define UUID_SERVCLASS_T_ADV_AUDIO_VOICE 0x6AD5 +#define UUID_SERVCLASS_T_ADV_AUDIO_CONN_LESS_MEDIA_SINK 0xFFA6 +#define UUID_SERVCLASS_T_ADV_AUDIO_ASSIST 0xFFA7 +#define UUID_SERVCLASS_T_ADV_AUDIO_DELEGATE 0xFFA8 +#define UUID_SERVCLASS_HAS 0x6AD2 +#define UUID_SERVCLASS_SOURCE_CONTEXT 0x2BCE +#define UUID_SERVCLASS_PACS_CT_SUPPORT 0x6AD4 +#define UUID_SERVCLASS_PACS_UMR_SUPPORT 0x6AD1 + +#define BTA_DM_LE_AUDIO_SEARCH_CMPL_EVT 7 + +#define BTA_DM_GROUP_DATA_TYPE 0x2E + +typedef struct { + RawAddress peer_address; + bool in_use =false; + bool is_t_audio_srvc_found = false; + bool is_has_found = false; + std::vector uuids; + int transport; //BTM_UseLeLink(remote_bda); + int conn_id = 0; + int8_t disc_progress = 0; + uint8_t gatt_if; + bool using_bredr_bonding = false; + uint16_t t_role_handle = 0; + uint16_t pacs_char_handle = 0; + bool csip_disc_progress = true; + bool is_csip_support = false; + bool gatt_disc_progress = false; +} tBTA_LE_AUDIO_DEV_INFO; + +typedef struct { + RawAddress p_addr; + RawAddress p_id_addr; + bool is_le_pairing = false; + bool in_use = false; + bool is_dumo_device = false; + uint8_t dev_type; + uint8_t transport; + bool sdp_disc_status = true; +} tBTA_DEV_PAIRING_CB; + +#define MAX_LEA_DEVICES 3 +typedef struct { + tBTA_DEV_PAIRING_CB bta_dev_pair_db[MAX_LEA_DEVICES]; + uint8_t num_devices; + bool is_pairing_progress = false; + std::map dev_addr_map; + std::map dev_rand_addr_map; + RawAddress pending_address; + bool is_sdp_discover = true; +} tBTA_LEA_PAIRING_DB; + + +typedef struct { + tBTA_LE_AUDIO_DEV_INFO bta_lea_dev_info[MAX_LEA_DEVICES]; + RawAddress pending_peer_addr; + RawAddress gatt_op_addr = RawAddress::kEmpty; + int num_lea_devices = 0; + bool bond_progress = false; +} tBTA_LE_AUDIO_DEV_CB; + +extern tBTA_LE_AUDIO_DEV_CB bta_le_audio_dev_cb; +extern bool is_remote_support_adv_audio(const RawAddress remote_bdaddr); +extern void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport); +extern bool is_le_audio_service(bluetooth::Uuid uuid); +extern int bta_is_adv_audio_valid_bdaddr(RawAddress p_bd_addr); +extern bool bta_is_le_audio_supported(RawAddress p_bd_addr); +extern bool bta_remote_device_is_dumo(RawAddress p_bd_addr); +extern RawAddress bta_get_rem_dev_id_addr(RawAddress p_bd_addr); +extern tBTA_DEV_PAIRING_CB* bta_get_lea_pair_cb(RawAddress peer_addr); +extern bool bta_lea_addr_match(RawAddress p_bd_addr); +extern void bta_dm_reset_lea_pairing_info(RawAddress p_addr); +extern bool bta_is_bredr_primary_transport(RawAddress p_bd_addr); +extern bool bta_is_remote_support_lea(RawAddress p_addr); +extern bool bta_remote_dev_identity_addr_match(RawAddress p_addr); +extern void bta_dm_lea_disc_complete(RawAddress p_bd_addr); +extern void bta_dm_csis_disc_complete(RawAddress p_bd_addr, bool status); +extern tBTA_LE_AUDIO_DEV_INFO* bta_get_lea_ctrl_cb(RawAddress peer_addr); +extern void bta_gap_gatt_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data); +extern void bta_get_adv_audio_role(RawAddress peer_address, uint16_t conn_id, + tGATT_STATUS status); +extern void bta_dm_csis_disc_complete(RawAddress p_bd_addr, bool status); +extern void bta_dm_lea_disc_complete(RawAddress p_bd_addr); +extern void bta_add_adv_audio_uuid(RawAddress peer_address, + tBTA_GATT_ID srvc_uuid); +extern tBTA_LE_AUDIO_DEV_INFO* bta_set_lea_ctrl_cb(RawAddress peer_addr); +extern void bta_dm_reset_adv_audio_dev_info(RawAddress p_addr); +extern void bta_dm_set_adv_audio_dev_info(tBTA_GATTC_OPEN* p_data); +extern bool is_adv_audio_group_supported(RawAddress rem_bda, int conn_id); +extern void bta_dm_lea_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +extern void bta_dm_adv_audio_gatt_conn(RawAddress p_bd_addr); +extern void bta_dm_adv_audio_close(RawAddress p_bd_addr); +extern tBTA_DEV_PAIRING_CB* bta_get_lea_pair_cb(RawAddress peer_addr); +extern tBTA_DEV_PAIRING_CB* bta_set_lea_pair_cb(RawAddress peer_addr); +extern void bta_dm_reset_lea_pairing_info(RawAddress p_addr); +extern void bta_dm_ble_adv_audio_idaddr_map(RawAddress p_bd_addr, + RawAddress p_id_addr); +extern bool bta_remote_dev_identity_addr_match(RawAddress p_addr); +extern bool bta_lea_is_le_pairing(RawAddress p_bd_addr); +extern bool bta_remote_device_is_dumo(RawAddress p_bd_addr); +extern RawAddress bta_get_rem_dev_id_addr(RawAddress p_bd_addr); +extern void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport); +extern int bta_is_adv_audio_valid_bdaddr(RawAddress p_bd_addr); +extern void bta_find_adv_audio_group_instance(uint16_t conn_id, tGATT_STATUS status, + RawAddress p_addr); +extern bool bta_is_remote_support_lea(RawAddress p_addr); +extern bool is_gatt_srvc_disc_pending(RawAddress rem_bda); +extern RawAddress bta_get_pseudo_addr_with_id_addr(RawAddress p_addr); +#endif /* BTA_DM_ADV_AUDIO_H*/ diff --git a/le_audio/system/bt/bta/include/bta_mcp_api.h b/le_audio/system/bt/bta/include/bta_mcp_api.h new file mode 100644 index 0000000000000000000000000000000000000000..de6dfc77d29524b9a552b63b0eee3ddd26282275 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_mcp_api.h @@ -0,0 +1,489 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#ifndef BTA_MCP_API_H +#define BTA_MCP_API_H + +#include +#include +#include +#include +#include +#include "bta_gatt_api.h" + +using bluetooth::Uuid; +using bluetooth::mcp_server::McpServerCallbacks; + + +#define MAX_PLAYER_NAME_SIZE GATT_MAX_ATTR_LEN +#define MAX_TRACK_TITLE_SIZE GATT_MAX_ATTR_LEN +#define MAX_RESPONSE_DATA_LEN GATT_MAX_ATTR_LEN +#define MAX_MCP_CONNECTION 5 + +#define TRACK_POSITION_UNAVAILABLE 0xFFFFFFFF +#define TRACK_DURATION_UNAVAILABLE 0xFFFFFFFF + +/* Media Control Point Opcodes Supported characteristics bit values */ +#define MCP_MEDIA_CONTROL_SUP_PLAY 1<<0 +#define MCP_MEDIA_CONTROL_SUP_PAUSE 1<<1 +#define MCP_MEDIA_CONTROL_SUP_FAST_REWIND 1<<2 +#define MCP_MEDIA_CONTROL_SUP_FAST_FORWARD 1<<3 +#define MCP_MEDIA_CONTROL_SUP_STOP 1<<4 +#define MCP_MEDIA_CONTROL_SUP_MOVE_RELATIVE 1<<5 +#define MCP_MEDIA_CONTROL_SUP_PREVIOUS_SEGMENT 1<<6 +#define MCP_MEDIA_CONTROL_SUP_NEXT_SEGMENT 1<<7 +#define MCP_MEDIA_CONTROL_SUP_FIRST_SEGMENT 1<<8 +#define MCP_MEDIA_CONTROL_SUP_LAST_SEGMENT 1<<9 +#define MCP_MEDIA_CONTROL_SUP_GOTO_SEGMENT 1<<10 +#define MCP_MEDIA_CONTROL_SUP_PREVIOUS_TRACK 1<<11 +#define MCP_MEDIA_CONTROL_SUP_NEXT_TRACK 1<<12 +#define MCP_MEDIA_CONTROL_SUP_FIRST_TRACK 1<<13 +#define MCP_MEDIA_CONTROL_SUP_LAST_TRACK 1<<14 +#define MCP_MEDIA_CONTROL_SUP_GOTO_TRACK 1<<15 +#define MCP_MEDIA_CONTROL_SUP_PREVIOUS_GROUP 1<<16 +#define MCP_MEDIA_CONTROL_SUP_NEXT_GROUP 1<<17 +#define MCP_MEDIA_CONTROL_SUP_FIRST_GROUP 1<<18 +#define MCP_MEDIA_CONTROL_SUP_LAST_GROUP 1<<19 +#define MCP_MEDIA_CONTROL_SUP_GOTO_GROUP 1<<20 + +//media control point opcodes +#define MCP_MEDIA_CONTROL_OPCODE_PLAY 0x01 +#define MCP_MEDIA_CONTROL_OPCODE_PAUSE 0x02 +#define MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND 0x03 +#define MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD 0x04 +#define MCP_MEDIA_CONTROL_OPCODE_STOP 0x05 +#define MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK 0x30 +#define MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK 0x31 +#define MCP_MEDIA_CONTROL_OPCODE_MOVE_RELATIVE 0x10 + +/* Playing Order Supported characteristic bit values */ +#define MCP_PLAYING_OREDR_SINGLE_ONCE 1<<0 +#define MCP_PLAYING_OREDR_SINGLE_REPEAT 1<<1 +#define MCP_PLAYING_OREDR_IN_ORDER_ONCE 1<<2 +#define MCP_PLAYING_OREDR_IN_ORDER_REPEAT 1<<3 +#define MCP_PLAYING_OREDR_OLDEST_ONCE 1<<4 +#define MCP_PLAYING_OREDR_OLDEST_REPEAT 1<<5 +#define MCP_PLAYING_OREDR_NEWEST_ONCE 1<<6 +#define MCP_PLAYING_OREDR_NEWEST_REPEAT 1<<7 +#define MCP_PLAYING_OREDR_SHUFFLE_ONCE 1<<8 +#define MCP_PLAYING_OREDR_SHUFFLE_REPEAT 1<<9 + +#define MCP_DEFAULT_MEDIA_CTRL_SUPP_FEAT MCP_MEDIA_CONTROL_SUP_PLAY| \ + MCP_MEDIA_CONTROL_SUP_PAUSE| \ + MCP_MEDIA_CONTROL_SUP_FAST_REWIND| \ + MCP_MEDIA_CONTROL_SUP_FAST_FORWARD| \ + MCP_MEDIA_CONTROL_SUP_STOP| \ + MCP_MEDIA_CONTROL_SUP_PREVIOUS_TRACK| \ + MCP_MEDIA_CONTROL_SUP_NEXT_TRACK + +typedef enum { + // TO-DO: Naming in such a way to distinguish BTIF and lower layer events + MCP_NONE_EVENT = 70, + MCP_INIT_EVENT, + MCP_CLEANUP_EVENT, + MCP_MEDIA_STATE_UPDATE, + MCP_MEDIA_PLAYER_NAME_UPDATE, + MCP_MEDIA_SUPPORTED_OPCODE_UPDATE, + MCP_MEDIA_CONTROL_POINT_UPDATE, + MCP_PLAYING_ORDER_SUPPORTED_UPDATE, + MCP_PLAYING_ORDER_UPDATE, + MCP_TRACK_CHANGED_UPDATE, + MCP_TRACK_POSITION_UPDATE, + MCP_TRACK_DURATION_UPDATE, + MCP_TRACK_TITLE_UPDATE, + MCP_CCID_UPDATE, + MCP_ACTIVE_DEVICE_UPDATE, + MCP_ACTIVE_PROFILE, + + //local event to handle in mcp state machine, + MCP_PLAYING_ORDER_SUPPORTED_READ, + MCP_PLAYING_ORDER_READ, + MCP_MEDIA_STATE_READ, + MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ, + MCP_MEDIA_PLAYER_NAME_READ, + MCP_TRACK_TITLE_READ, + MCP_TRACK_POSITION_READ, + MCP_TRACK_DURATION_READ, + MCP_CCID_READ, + MCP_SEEKING_SPEED_READ, + MCP_MEDIA_STATE_READ_DESCRIPTOR, + MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR, + MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ, + MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ, + MCP_TRACK_CHANGED_DESCRIPTOR_READ, + MCP_TRACK_TITLE_DESCRIPTOR_READ, + MCP_TRACK_POSITION_DESCRIPTOR_READ, + MCP_TRACK_DURATION_DESCRIPTOR_READ, + MCP_PLAYING_ORDER_DESCRIPTOR_READ, + MCP_MEDIA_STATE_DESCRIPTOR_WRITE, + MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE, + MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE, + MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE, + MCP_TRACK_CHANGED_DESCRIPTOR_WRITE, + MCP_TRACK_TITLE_DESCRIPTOR_WRITE, + MCP_TRACK_POSITION_DESCRIPTOR_WRITE, + MCP_TRACK_DURATION_DESCRIPTOR_WRITE, + MCP_PLAYING_ORDER_DESCRIPTOR_WRITE, + MCP_MEDIA_CONTROL_POINT_WRITE, + MCP_PLAYING_ORDER_WRITE, + MCP_TRACK_POSITION_WRITE, +//device state event + MCP_NOTIFY_ALL, + MCP_WRITE_RSP, + MCP_READ_RSP, + MCP_DESCRIPTOR_WRITE_RSP, + MCP_DESCRIPTOR_READ_RSP, + MCP_CONNECTION, + MCP_DISCONNECTION, + MCP_CONNECTION_UPDATE, + MCP_CONGESTION_UPDATE, + MCP_PHY_UPDATE, + MCP_MTU_UPDATE, + MCP_SET_ACTIVE_DEVICE, + MCP_CONNECTION_CLOSE_EVENT, + MCP_BOND_STATE_CHANGE_EVENT, +//media write op code event + MCP_MEDIA_CONTROL_PLAY_READ_REQ, + MCP_MEDIA_CONTROL_PAUSE_REQ, + MCP_MEDIA_CONTROL_FAST_FORWARD_REQ, + MCP_MEDIA_CONTROL_FAST_REWIND_REQ, + MCP_MEDIA_CONTROL_MOVE_RELATIVE_REQ, + MCP_MEDIA_CONTROL_STOP_REQ, + MCP_MEDIA_CONTROL_NEXT_TRACK_REQ, + MCP_MEDIA_CONTROL_PREVIOUS_TRACK_REQ, + MCP_PLAYING_OREDR_SHUFFLE_REPEAT_REQ, + +}mcp_event_t; + +//state handler declaration +typedef bool (*mcp_handler)(uint32_t event, void* param, uint8_t state); +typedef enum { + //media conrol point success or error code + MCP_STATUS_SUCCESS = 1, + MCP_OPCODE_NOT_SUPPORTED, + MCP_MEDIA_PLAYER_INACTIVE, + MCP_COMMAND_CANNOT_COMPLETED, + + BT_STATUS_DEVICE_NOT_CONNECTED, + BT_STATUS_HANLDE_NOT_MATCHED, +}mcp_error_t; + +typedef enum { + MCP_DISCONNECTED = 0x00, + MCP_CONNECTED, + MCP_MAX_DEVICE_STATE +} remote_device_state_t; + +typedef enum { + MCP_STATE_INACTIVE = 0x00, + MCP_STATE_PLAYING, + MCP_STATE_PAUSE, + MCP_STATE_SEEKING, + MCP_MAX_MEDIA_STATE +} mcp_state_t; + +typedef struct { + uint8_t media_state; + uint16_t media_ctrl_point; + uint32_t media_supported_feature; + uint8_t player_name[MAX_PLAYER_NAME_SIZE]; + uint16_t player_name_len; + uint8_t track_changed; + int32_t duration; + int32_t position; + uint16_t playing_order_supported; + uint8_t playing_order_value; + uint8_t title[MAX_TRACK_TITLE_SIZE]; + uint16_t track_title_len; + uint8_t ccid; + uint8_t seeking_speed; + mcp_handler MediaStateHandlerPointer[MCP_MAX_MEDIA_STATE]; +} MediaPlayerInfo_t; + +typedef struct { + int server_if; + Uuid mcs_service_uuid; + Uuid media_state_uuid; + Uuid media_player_name_uuid; + Uuid media_control_point_uuid; + Uuid media_control_point_opcode_supported_uuid; + Uuid track_changed_uuid; + Uuid track_title_uuid; + Uuid track_duration_uuid; + Uuid track_position_uuid; + Uuid playing_order_supported_uuid; + Uuid playing_order_uuid; + Uuid ccid_uuid; + Uuid seeking_speed_uuid; + //handle for characteristics + uint16_t media_state_handle; + uint16_t media_player_name_handle; + uint16_t media_control_point_opcode_supported_handle; + uint16_t media_control_point_handle; + uint16_t track_changed_handle; + uint16_t track_title_handle; + uint16_t track_duration_handle; + uint16_t track_position_handle; + uint16_t playing_order_supported_handle; + uint16_t playing_order_handle; + uint16_t ccid_handle; + uint16_t seeking_speed_handle; + uint16_t media_state_desc; + uint16_t media_player_name_desc; + uint16_t media_control_point_opcode_supported_desc; + uint16_t media_control_point_desc; + uint16_t track_changed_desc; + uint16_t track_title_desc; + uint16_t track_duration_desc; + uint16_t track_position_desc; + uint16_t playing_order_supported_desc; + uint16_t playing_order_desc; + uint16_t ccid_desc; + uint16_t seeking_speed_desc; +} mcsServerServiceInfo_t; + +typedef struct { + remote_device_state_t state; + uint8_t active_profile; + uint16_t media_state_notify; + uint16_t media_player_name_notify; + uint16_t media_control_point_notify; + uint16_t media_control_point_opcode_supported_notify; + uint16_t track_changed_notify; + uint16_t track_duration_notify; + uint16_t track_title_notify; + uint16_t track_position_notify; + uint16_t playing_order_notify; + uint16_t seeking_speed_notify; + bool congested; + int conn_id; + int trans_id; + int timeout; + int latency; + int interval; + int rx_phy; + int tx_phy; + int mtu; + RawAddress peer_bda; + mcp_handler DeviceStateHandlerPointer[MCP_MAX_DEVICE_STATE]; +}RemoteDevice; + +typedef struct { + std::vector address; + int set_id; +}ActiveDevice; + +typedef struct { + uint8_t status; + uint16_t notification; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; + bool need_rsp; + bool prep_rsp; + //is to send notification + uint8_t *data; + uint16_t len; +}tMCP_DESC_WRITE; + +typedef struct { + uint8_t status; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; +}tMCP_DESC_READ; + +typedef struct { + bool is_long; + uint8_t status; + uint32_t trans_id; + uint32_t char_handle; +}tMCP_READ; + +typedef struct { + uint8_t status; + bool need_rsp; + bool prep_rsp; + uint16_t offset; + uint32_t trans_id; + uint16_t char_handle; + //is to send notification + uint8_t data[GATT_MAX_ATTR_LEN]; +}tMCP_WRITE; + +typedef struct { + uint8_t status; + RemoteDevice remoteDevice; +}tMCP_CONNECTION; + +typedef struct { + uint8_t status; + int timeout; + int latency; + int interval; +}tMCP_CONN_UPDATE; + +typedef struct { + uint8_t status; + RemoteDevice *remoteDevice; +}tMCP_DISCONNECTION; + +typedef struct { + bool congested; + RemoteDevice *remoteDevice; +} tMCP_CONGESTION; + +typedef struct { + uint8_t status; + uint8_t tx_phy; + uint8_t rx_phy; + RemoteDevice *remoteDevice; +} tMCP_PHY; + +typedef struct { + uint8_t status; + uint16_t mtu; + RemoteDevice *remoteDevice; +} tMCP_MTU; + +typedef struct { + uint8_t state; +}tMCP_MEDIA_STATE; + +typedef struct { + uint32_t req_opcode; + uint8_t result; +}tMCP_MEDIA_CONTROL_POINT; + +typedef struct { + uint32_t supported; +}tMCP_MEDIA_OPCODE_SUPPORT; + +typedef struct { + uint8_t *name; + uint16_t len; +}tMCP_MEDIA_PLAYER_NAME; + +typedef struct { + bool status; +}tMCP_TRACK_CHANGED; + +typedef struct { + int32_t position; +}tMCP_TRACK_POSTION; + +typedef struct { + uint32_t duration; +}tMCP_TRACK_DURATION; + +typedef struct { + uint8_t *title; + uint16_t len; +}tMCP_TRACK_TITLE; + +typedef struct { + uint8_t ccid; +}tMCP_CONTENT_CONTROL_ID; + +typedef struct { + uint8_t seek_speed; +}tMCP_SEEKING_SPEED_CONTROL_ID; + +typedef struct { + RawAddress addr; +}tMCP_CONNECTION_CLOSE; + +typedef struct { + RawAddress addr; + int state; +}tMCP_BOND_STATE_CHANGE; + +typedef struct { + uint32_t order_supported; +}tMCP_PLAYING_ORDER_SUPPORT; + +typedef struct { + uint8_t order; +}tMCP_PLAYING_ORDER; + +typedef struct { + RawAddress address; + uint16_t set_id; + uint8_t profile; +}tMCP_SET_ACTIVE_DEVICE; + +typedef struct { + uint8_t *data; + uint16_t len; +}tMCP_MEDIA_UPDATE; + +union tMCP_MEDIA_OPERATION{ + tMCP_MEDIA_UPDATE MediaUpdateOp; + tMCP_SET_ACTIVE_DEVICE SetActiveDeviceOp; + tMCP_DESC_WRITE WriteDescOp; + tMCP_DESC_READ ReadDescOp; + tMCP_WRITE WriteOp; + tMCP_READ ReadOp; + tMCP_CONNECTION ConnectionOp; + tMCP_CONN_UPDATE ConnectionUpdateOp; + tMCP_DISCONNECTION DisconnectionOp; + tMCP_CONGESTION CongestionOp; + tMCP_MTU MtuOp; + tMCP_PHY PhyOp; +}; + +typedef union tMCP_MEDIA_OPERATION tMCP_MEDIA_OPERATION; + +struct mcp_resp_t { + uint32_t event; + uint16_t handle; + uint16_t status; + RemoteDevice *remoteDevice; + tGATTS_RSP rsp_value; + tMCP_MEDIA_OPERATION oper; +}; + +typedef struct mcp_resp_t mcp_resp_t; + +class McpServer { + public: + virtual ~McpServer() = default; + static void Initialize(bluetooth::mcp_server::McpServerCallbacks* callbacks, Uuid ap_id); + static void CleanUp(); + static McpServer* Get(); + static bool isMcpServiceRunnig(); + virtual void MediaState(uint8_t state) = 0; + virtual void MediaPlayerName(uint8_t* player_name) = 0; + virtual void MediaControlPointOpcodeSupported(uint32_t feature) = 0; + virtual void MediaControlPoint(uint8_t value) = 0; + virtual void TrackChanged(bool status) = 0; + virtual void TrackDuration(int32_t duration) = 0; + virtual void TrackTitle(uint8_t* title) = 0; + virtual void TrackPosition(int32_t position) = 0; + virtual void PlayingOrderSupported(uint16_t order) = 0; + virtual void PlayingOrder(uint8_t value) = 0; + virtual void SetActiveDevice(const RawAddress& address, int setId, int profile) = 0; + virtual void ContentControlId(uint8_t ccid) = 0; + virtual void DisconnectMcp(const RawAddress& address) = 0; + virtual void BondStateChange(const RawAddress& address, int state) = 0; +}; + +void McpCongestionUpdate(mcp_resp_t *p_data); + +#endif // BTA_MCP_API_H diff --git a/le_audio/system/bt/bta/include/bta_pacs_client_api.h b/le_audio/system/bt/bta/include/bta_pacs_client_api.h new file mode 100644 index 0000000000000000000000000000000000000000..1a364d8dc5c5e2f0c1e2eb767906ddabc0fd3b12 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_pacs_client_api.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace bluetooth { +namespace bap { +namespace pacs { + +class PacsClient { + public: + virtual ~PacsClient() = default; + + static void Initialize(bluetooth::bap::pacs::PacsClientCallbacks* callbacks); + static void CleanUp(uint16_t client_id); + static PacsClient* Get(); + virtual void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) = 0; + virtual void Disconnect(uint16_t client_id, + const RawAddress& address) = 0; + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + virtual void GetAudioAvailability(uint16_t client_id, + const RawAddress& address) = 0; +}; + +} // namespace pacs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/include/bta_vcp_controller_api.h b/le_audio/system/bt/bta/include/bta_vcp_controller_api.h new file mode 100644 index 0000000000000000000000000000000000000000..8518262a91c4b9e19a5323ecbbc68df480eaccdb --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_vcp_controller_api.h @@ -0,0 +1,148 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +enum { + BTA_VCP_DISCONNECTED = 0x0, + BTA_VCP_CONNECTING, + BTA_VCP_CONNECTED, + BTA_VCP_DISCONNECTING, +}; + +enum { + VCS_VOLUME_STATE_READ_CMPL_EVT = 0x0, + VCS_VOLUME_FLAGS_READ_CMPL_EVT, + VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT, + VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT, +}; + +enum { + VCS_CONTROL_POINT_OP_REL_VOLUME_DOWN = 0x0, + VCS_CONTROL_POINT_OP_REL_VOLUME_UP, + VCS_CONTROL_POINT_OP_UNMUTE_REL_VOLUME_DOWN, + VCS_CONTROL_POINT_OP_UNMUTE_REL_VOLUME_UP, + VCS_CONTROL_POINT_OP_SET_ABS_VOL, + VCS_CONTROL_POINT_OP_UNMUTE, + VCS_CONTROL_POINT_OP_MUTE, +}; + +enum { + VCS_UNMUTE_STATE = 0x0, + VCS_MUTE_STATE, +}; + +typedef struct { + uint8_t op_id; + uint8_t change_counter; + uint8_t volume_setting; +} SetAbsVolumeOp; + +typedef struct { + uint8_t op_id; + uint8_t change_counter; +} MuteOp; + +typedef struct { + uint8_t op_id; + uint8_t change_counter; +} UnmuteOp; + +struct VolumeState { + uint8_t volume_setting; + uint8_t mute; + uint8_t change_counter; + + VolumeState() + : volume_setting(0), + mute(0), + change_counter(0) {} +}; + +struct VolumeControlService { + uint16_t volume_state_handle; + uint16_t volume_control_point_handle; + uint16_t volume_flags_handle; + uint16_t volume_state_ccc_handle; + uint16_t volume_flags_ccc_handle; + + VolumeState volume_state; + uint8_t volume_flags; + uint8_t pending_volume_setting; + uint8_t pending_mute_setting; + uint8_t retry_cmd; + + VolumeControlService() + : volume_state_handle(0), + volume_control_point_handle(0), + volume_flags_handle(0), + volume_state_ccc_handle(0), + volume_flags_ccc_handle(0), + volume_state(), + volume_flags(0), + pending_volume_setting(0), + pending_mute_setting(0), + retry_cmd(0) {} +}; + +struct RendererDevice { + RawAddress address; + uint16_t conn_id; + uint8_t state; + bool bg_conn; + bool service_changed_rcvd; + VolumeControlService vcs; + + RendererDevice(const RawAddress& address) + : address(address), + conn_id(0), + state(BTA_VCP_DISCONNECTED), + bg_conn(false), + service_changed_rcvd(false), + vcs() {} + + RendererDevice() : RendererDevice(RawAddress::kEmpty) {} +}; + +class VcpController { + public: + virtual ~VcpController() = default; + + static void Initialize(bluetooth::vcp_controller::VcpControllerCallbacks* callbacks); + static void CleanUp(); + static VcpController* Get(); + static bool IsVcpControllerRunning(); + static int GetDeviceCount(); + + virtual void Connect(const RawAddress& address, bool isDirect) = 0; + virtual void Disconnect(const RawAddress& address) = 0; + virtual void SetAbsVolume(const RawAddress& address, uint8_t volume) = 0; + virtual void Mute(const RawAddress& address) = 0; + virtual void Unmute(const RawAddress& address) = 0; +}; + diff --git a/le_audio/system/bt/bta/include/connected_iso_api.h b/le_audio/system/bt/bta/include/connected_iso_api.h new file mode 100644 index 0000000000000000000000000000000000000000..74e16ccfc40748d61965997bc6c2288988f2b813 --- /dev/null +++ b/le_audio/system/bt/bta/include/connected_iso_api.h @@ -0,0 +1,111 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#pragma once + +#include +#include + +#include "stack/include/bt_types.h" +#include + +namespace bluetooth { +namespace bap { +namespace cis { + +using bluetooth::bap::ucast::CISConfig; +using bluetooth::bap::ucast::CIGConfig; + +constexpr uint8_t DIR_TO_AIR = 0x1 << 0; +constexpr uint8_t DIR_FROM_AIR = 0x1 << 1; + +typedef uint8_t sdu_interval_t[3]; + +enum class CisState { + INVALID = 0, + READY, + DESTROYING, + ESTABLISHING, + ESTABLISHED +}; + +enum class CigState { + INVALID = 0, + IDLE, + CREATING, + CREATED, + REMOVING +}; + +enum IsoHciStatus { + ISO_HCI_SUCCESS = 0, + ISO_HCI_FAILED, + ISO_HCI_IN_PROGRESS +}; + +class CisInterfaceCallbacks { + public: + virtual ~CisInterfaceCallbacks() = default; + + /** Callback for connection state change */ + virtual void OnCigState(uint8_t cig_id, CigState state) = 0; + + virtual void OnCisState(uint8_t cig_id, uint8_t cis_id, + uint8_t direction, CisState state) = 0; +}; + +class CisInterface { + public: + virtual ~CisInterface() = default; + + static void Initialize(CisInterfaceCallbacks* callbacks); + static void CleanUp(); + static CisInterface* Get(); + + virtual CigState GetCigState(const uint8_t &cig_id); + + virtual CisState GetCisState(const uint8_t &cig_id, uint8_t cis_id); + + virtual uint8_t GetCisCount(const uint8_t &cig_id) = 0; + + virtual IsoHciStatus CreateCig(RawAddress client_peer_bda, + bool reconfig, + CIGConfig &cig_config, + std::vector &cis_configs) = 0; + + virtual IsoHciStatus RemoveCig(RawAddress peer_bda, + uint8_t cig_id) = 0; + + virtual IsoHciStatus CreateCis(uint8_t cig_id, std::vector cis_ids, + RawAddress peer_bda) = 0; + + virtual IsoHciStatus DisconnectCis(uint8_t cig_id, uint8_t cis_id, + uint8_t direction) = 0; + + virtual IsoHciStatus SetupDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t direction, + uint8_t path_id) = 0; + + virtual IsoHciStatus RemoveDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t direction) = 0; +}; + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/mcp/bta_mcp_main.cc b/le_audio/system/bt/bta/mcp/bta_mcp_main.cc new file mode 100644 index 0000000000000000000000000000000000000000..b920ec25f4489e0090659b54518ca2668bd30368 --- /dev/null +++ b/le_audio/system/bt/bta/mcp/bta_mcp_main.cc @@ -0,0 +1,3089 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + + + +/****************************************************************************** + * + * This file contains the MCP server main functions and state machine. + * + ******************************************************************************/ + +#include "bta_api.h" +#include "bt_target.h" +#include "bta_mcp_api.h" +#include "gatts_ops_queue.h" +#include "btm_int.h" +#include "device/include/controller.h" +#include "osi/include/properties.h" +#include "bta_sys.h" +#include "btif_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using bluetooth::Uuid; +using bluetooth::bap::GattsOpsQueue; + +class McpServerImpl; +static McpServerImpl *instance; + +//global variables +mcsServerServiceInfo_t mcsServerServiceInfo; +MediaPlayerInfo_t mediaPlayerInfo; + +void HandleMcsEvent(uint32_t event, void* param); + +typedef base::Callback service)> + OnMcpServiceAdded; + +static void OnMcpServiceAddedCb(uint8_t status, int serverIf, + std::vector service); + +/* Media state handlers */ +static bool MediaStateInactiveHandler(uint32_t event, void* param, uint8_t state); +static bool MediaStatePauseHandler(uint32_t event, void* param, uint8_t state); +static bool MediaStatePlayingHandler(uint32_t event, void* param, uint8_t state); +static bool MediaStateSeekingHandler(uint32_t event, void* param, uint8_t state); + +/* Connection state machine handlers */ +static bool DeviceStateConnectionHandler(uint32_t event, void* param, uint8_t state); +static bool DeviceStateDisconnectedHandler(uint32_t event, void* param, uint8_t state); + +Uuid MCS_UUID = Uuid::FromString("1848"); +Uuid GMCS_UUID = Uuid::FromString("1849"); + +Uuid DESCRIPTOR_UUID = Uuid::FromString("2902"); + +Uuid GMCS_MEDIA_STATE_UUID = Uuid::FromString("2BA3"); +Uuid GMCS_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED = Uuid::FromString("2BA5"); +Uuid GMCS_MEDIA_PLAYER_NAME_UUID = Uuid::FromString("2B93"); +Uuid GMCS_MEDIA_CONTROL_POINT = Uuid::FromString("2BA4"); +Uuid GMCS_TRACK_CHANGED = Uuid::FromString("2B96"); +Uuid GMCS_TRACK_TITLE = Uuid::FromString("2B97"); +Uuid GMCS_TRACK_DURATION = Uuid::FromString("2B98"); +Uuid GMCS_TRACK_POSITION = Uuid::FromString("2B99"); +Uuid GMCS_PLAYING_ORDER_SUPPORTED = Uuid::FromString("2BA2"); +Uuid GMCS_PLAYING_ORDER = Uuid::FromString("2BA1"); +Uuid GMCS_CONTENT_CONTROLID = Uuid::FromString("2BBA"); +Uuid GMCS_SEEKING_SPEED_UUID = Uuid::FromString("2B9B"); + + + bool is_pts_running() { + char value[PROPERTY_VALUE_MAX] = {'\0'}; + bool pts_test_enabled = false; + osi_property_get("persist.vendor.service.bt.mcs.pts", value, "false"); + pts_test_enabled = (strcmp(value, "true") == 0); + LOG(INFO) << "pts test enabled " << pts_test_enabled; + return pts_test_enabled; + } + + int playing_order_opcode(int data) { + int event = 0; + if (data & MCP_PLAYING_OREDR_SHUFFLE_REPEAT) { + event = MCP_PLAYING_OREDR_SHUFFLE_REPEAT_REQ; + } else + LOG(INFO) << "opcode not matched or not supported"; + return event; + } + + bool is_opcode_supported(int data) { + LOG(INFO) << __func__ << "data " << data << "media_supported_feature " << mediaPlayerInfo.media_supported_feature; + switch (data) { + case MCP_MEDIA_CONTROL_OPCODE_PLAY: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_PLAY); + case MCP_MEDIA_CONTROL_OPCODE_PAUSE: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_PAUSE); + case MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_FAST_REWIND); + case MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_FAST_FORWARD); + case MCP_MEDIA_CONTROL_OPCODE_STOP: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_STOP); + case MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_PREVIOUS_TRACK); + case MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_NEXT_TRACK); + + // Fallthrough for all unknown key mappings + default: + LOG(INFO) << __func__ << "opcode is not supported"; + return false; + } + } + +const char* get_mcp_event_name(uint32_t event) { + switch (event) { + CASE_RETURN_STR(MCP_INIT_EVENT) + CASE_RETURN_STR(MCP_CLEANUP_EVENT) + CASE_RETURN_STR(MCP_MEDIA_STATE_UPDATE) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_UPDATE) + CASE_RETURN_STR(MCP_MEDIA_SUPPORTED_OPCODE_UPDATE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_UPDATE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_SUPPORTED_UPDATE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_UPDATE) + CASE_RETURN_STR(MCP_TRACK_CHANGED_UPDATE) + CASE_RETURN_STR(MCP_TRACK_POSITION_UPDATE) + CASE_RETURN_STR(MCP_TRACK_DURATION_UPDATE) + CASE_RETURN_STR(MCP_TRACK_TITLE_UPDATE) + CASE_RETURN_STR(MCP_CCID_UPDATE) + CASE_RETURN_STR(MCP_ACTIVE_DEVICE_UPDATE) + CASE_RETURN_STR(MCP_ACTIVE_PROFILE) + + //local event to handle in mcp state machine, + CASE_RETURN_STR(MCP_PLAYING_ORDER_SUPPORTED_READ) + CASE_RETURN_STR(MCP_PLAYING_ORDER_READ) + CASE_RETURN_STR(MCP_MEDIA_STATE_READ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_READ) + CASE_RETURN_STR(MCP_TRACK_TITLE_READ) + CASE_RETURN_STR(MCP_TRACK_POSITION_READ) + CASE_RETURN_STR(MCP_TRACK_DURATION_READ) + CASE_RETURN_STR(MCP_CCID_READ) + CASE_RETURN_STR(MCP_SEEKING_SPEED_READ) + CASE_RETURN_STR(MCP_MEDIA_STATE_READ_DESCRIPTOR) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_CHANGED_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_TITLE_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_POSITION_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_DURATION_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_PLAYING_ORDER_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_MEDIA_STATE_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_CHANGED_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_TITLE_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_POSITION_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_DURATION_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_WRITE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_WRITE) + CASE_RETURN_STR(MCP_TRACK_POSITION_WRITE) + + CASE_RETURN_STR(MCP_NOTIFY_ALL) + CASE_RETURN_STR(MCP_WRITE_RSP) + CASE_RETURN_STR(MCP_READ_RSP) + CASE_RETURN_STR(MCP_DESCRIPTOR_WRITE_RSP) + CASE_RETURN_STR(MCP_DESCRIPTOR_READ_RSP) + CASE_RETURN_STR(MCP_CONNECTION) + CASE_RETURN_STR(MCP_DISCONNECTION) + CASE_RETURN_STR(MCP_CONNECTION_UPDATE) + CASE_RETURN_STR(MCP_CONGESTION_UPDATE) + CASE_RETURN_STR(MCP_PHY_UPDATE) + CASE_RETURN_STR(MCP_MTU_UPDATE) + CASE_RETURN_STR(MCP_SET_ACTIVE_DEVICE) + CASE_RETURN_STR(MCP_CONNECTION_CLOSE_EVENT) + CASE_RETURN_STR(MCP_BOND_STATE_CHANGE_EVENT) + //media write op code event + CASE_RETURN_STR(MCP_MEDIA_CONTROL_PLAY_READ_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_PAUSE_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_FAST_FORWARD_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_FAST_REWIND_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_MOVE_RELATIVE_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_STOP_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_NEXT_TRACK_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_PREVIOUS_TRACK_REQ) + CASE_RETURN_STR(MCP_PLAYING_OREDR_SHUFFLE_REPEAT_REQ) + default: + return "Unknown Event"; + } +} + +const char* get_mcp_media_state_name(uint8_t media_state) { + switch (media_state) { + CASE_RETURN_STR(MCP_STATE_INACTIVE) + CASE_RETURN_STR(MCP_STATE_PLAYING) + CASE_RETURN_STR(MCP_STATE_PAUSE) + CASE_RETURN_STR(MCP_STATE_SEEKING) + default: + return "Unknown Media State"; + } +} + +class RemoteDevices { + private: + ActiveDevice activeDevice; + //int max_connection; + public: + bool Add(RemoteDevice device) { + if (devices.size() == MAX_MCP_CONNECTION) { + return false; + } + if (FindByAddress(device.peer_bda) != nullptr) return false; + device.DeviceStateHandlerPointer[MCP_DISCONNECTED] = DeviceStateDisconnectedHandler; + device.DeviceStateHandlerPointer[MCP_CONNECTED] = DeviceStateConnectionHandler; + devices.push_back(device); + return true; + } + + void Remove(RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->peer_bda != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + RemoteDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const RemoteDevice& device) { + return device.peer_bda == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + RemoteDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const RemoteDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector GetRemoteDevices() { + return devices; + } + std::vector FindNotifyDevices(uint16_t handle) { + std::vector notify_devices; + for (size_t it = 0; it != devices.size(); it++){ + if(mcsServerServiceInfo.media_state_handle == handle && + devices[it].media_state_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.media_player_name_handle == handle && + devices[it].media_player_name_notify) { + notify_devices.push_back(devices[it]); + } else if (mcsServerServiceInfo.media_control_point_opcode_supported_handle == handle && + devices[it].media_control_point_opcode_supported_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.media_control_point_handle == handle && + devices[it].media_control_point_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_changed_handle == handle && + devices[it].track_changed_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_title_handle == handle && + devices[it].track_title_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_duration_handle == handle && + devices[it].track_duration_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_position_handle == handle && + devices[it].track_position_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.playing_order_handle == handle && + devices[it].playing_order_notify) { + notify_devices.push_back(devices[it]); + } + } + return notify_devices; + } + void AddSetActiveDevice(tMCP_SET_ACTIVE_DEVICE *device) { + if (device->set_id == activeDevice.set_id) { + activeDevice.address.push_back(device->address); + } else { + activeDevice.address.clear(); + activeDevice.set_id = device->set_id; + activeDevice.address.push_back(device->address); + } + } + + bool FindActiveDevice(RemoteDevice *remoteDevice) { + bool flag = false; + for (auto& it : activeDevice.address) { + if(remoteDevice->peer_bda == it) { + flag = true; + break; + } + } + return flag; + } + + std::vector devices; +}; + + +class McpServerImpl : public McpServer { + bluetooth::mcp_server::McpServerCallbacks* callbacks; + Uuid app_uuid; + + public: + RemoteDevices remoteDevices; + virtual ~McpServerImpl() = default; + + + McpServerImpl(bluetooth::mcp_server::McpServerCallbacks* callback, Uuid uuid) + :callbacks(callback), + app_uuid(uuid){ + LOG(INFO) << "McpServerImpl gatts app register"; + HandleMcsEvent(MCP_INIT_EVENT, &app_uuid); + + } + + void SetActiveDevice(const RawAddress& address, int setId, int profile) { + LOG(INFO) << __func__ ; + tMCP_SET_ACTIVE_DEVICE SetActiveDeviceOp; + SetActiveDeviceOp.set_id = setId; + SetActiveDeviceOp.address = address; + SetActiveDeviceOp.profile = profile; + HandleMcsEvent(MCP_ACTIVE_DEVICE_UPDATE, &SetActiveDeviceOp); + } + + void MediaState(uint8_t state) { + LOG(INFO) << __func__ << " state: " << unsigned(state); + tMCP_MEDIA_STATE MediaStateOp; + MediaStateOp.state = state; + HandleMcsEvent(MCP_MEDIA_STATE_UPDATE, &MediaStateOp); + } + + void MediaPlayerName(uint8_t* player_name) { + LOG(INFO) << __func__; + tMCP_MEDIA_PLAYER_NAME MediaPlayerNameOp; + MediaPlayerNameOp.name = player_name; + MediaPlayerNameOp.len = strlen((char *)player_name); + if(MediaPlayerNameOp.len != 0) + HandleMcsEvent(MCP_MEDIA_PLAYER_NAME_UPDATE, &MediaPlayerNameOp); + } + + void MediaControlPointOpcodeSupported(uint32_t feature) { + LOG(INFO) << __func__; + tMCP_MEDIA_OPCODE_SUPPORT MediaControlPointOpcodeSupportedOp; + MediaControlPointOpcodeSupportedOp.supported = feature; + HandleMcsEvent(MCP_MEDIA_SUPPORTED_OPCODE_UPDATE, &MediaControlPointOpcodeSupportedOp); + } + + void MediaControlPoint(uint8_t value) { + LOG(INFO) << __func__; + tMCP_MEDIA_CONTROL_POINT MediaControlPoint; + MediaControlPoint.req_opcode = value; + MediaControlPoint.result = MCP_STATUS_SUCCESS; // success + HandleMcsEvent(MCP_MEDIA_CONTROL_POINT_UPDATE, &MediaControlPoint); + } + + void TrackChanged(bool status) { + LOG(INFO) << __func__; + tMCP_TRACK_CHANGED TrackChangedOp; + TrackChangedOp.status = status; + HandleMcsEvent(MCP_TRACK_CHANGED_UPDATE, &TrackChangedOp); + } + + void TrackTitle(uint8_t* track_name) { + LOG(INFO) << __func__; + tMCP_TRACK_TITLE TrackTitleOp; + TrackTitleOp.title = track_name; + TrackTitleOp.len = strlen((char *)track_name); + if (TrackTitleOp.len != 0) + HandleMcsEvent(MCP_TRACK_TITLE_UPDATE, &TrackTitleOp); + } + + void TrackDuration(int32_t duration) { + LOG(INFO) << __func__; + tMCP_TRACK_DURATION TrackDurationOp; + TrackDurationOp.duration = duration; + HandleMcsEvent(MCP_TRACK_DURATION_UPDATE, &TrackDurationOp); + } + + void TrackPosition(int32_t position) { + LOG(INFO) << __func__; + tMCP_TRACK_POSTION TrackPositionOp; + TrackPositionOp.position = position; + HandleMcsEvent(MCP_TRACK_POSITION_UPDATE, &TrackPositionOp); + } + + void PlayingOrderSupported(uint16_t order) { + LOG(INFO) << __func__; + tMCP_PLAYING_ORDER_SUPPORT PlayingOrderSupportedOp; + PlayingOrderSupportedOp.order_supported = order; + HandleMcsEvent(MCP_PLAYING_ORDER_SUPPORTED_UPDATE, &PlayingOrderSupportedOp); + } + + void PlayingOrder(uint8_t value) { + LOG(INFO) << __func__; + tMCP_PLAYING_ORDER PlayingOrderOp; + PlayingOrderOp.order = value; + HandleMcsEvent(MCP_PLAYING_ORDER_UPDATE, &PlayingOrderOp); + } + + void ContentControlId(uint8_t ccid) { + LOG(INFO) << __func__; + tMCP_CONTENT_CONTROL_ID ContentControlIdOp; + ContentControlIdOp.ccid = ccid; + HandleMcsEvent(MCP_CCID_UPDATE, &ContentControlIdOp); + } + + void DisconnectMcp(const RawAddress& bd_addr) { + LOG(INFO) << __func__; + tMCP_CONNECTION_CLOSE ConnectClosingOp; + ConnectClosingOp.addr = bd_addr; + HandleMcsEvent(MCP_CONNECTION_CLOSE_EVENT, &ConnectClosingOp); + } + + void BondStateChange(const RawAddress& bd_addr, int state) { + LOG(INFO) << __func__; + tMCP_BOND_STATE_CHANGE BondStateChangeOP; + BondStateChangeOP.addr = bd_addr; + BondStateChangeOP.state = state; + HandleMcsEvent(MCP_BOND_STATE_CHANGE_EVENT, &BondStateChangeOP); + } + + void OnConnectionStateChange(uint8_t state, const RawAddress& address) { + LOG(INFO) << __func__ << " bta"; + callbacks->OnConnectionStateChange(state, address); + } + + void MediaControlPointChangeReq(uint8_t state, const RawAddress& address) { + callbacks->MediaControlPointChangeReq(state, address); + LOG(INFO) << __func__; + } + + void TrackPositionChangeReq(int32_t position) { + callbacks->TrackPositionChangeReq(position); + LOG(INFO) << __func__; + } + + void PlayingOrderChangeReq(uint16_t playingOrder) { + callbacks->PlayingOrderChangeReq(playingOrder); + LOG(INFO) << __func__; + } + +}; + + +void McpServer::CleanUp() { + HandleMcsEvent(MCP_CLEANUP_EVENT, NULL); + delete instance; + instance = nullptr; +} + +McpServer* McpServer::Get() { + CHECK(instance); + return instance; +} + +void McpServer::Initialize(bluetooth::mcp_server::McpServerCallbacks* callbacks, Uuid uuid) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + } else { + instance = new McpServerImpl(callbacks, uuid); + } +} + +bool McpServer::isMcpServiceRunnig() { return instance; } + +static std::vector McpAddService(int server_if) { + + std::vector mcs_services; + mcs_services.clear(); + //service + btgatt_db_element_t service = {.uuid = GMCS_UUID, .type = BTGATT_DB_PRIMARY_SERVICE, 0}; + mcs_services.push_back(service); + mcsServerServiceInfo.mcs_service_uuid = service.uuid; + + //media state service + btgatt_db_element_t mcs_media_state_char = {.uuid = GMCS_MEDIA_STATE_UUID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + mcs_services.push_back(mcs_media_state_char); + mcsServerServiceInfo.media_state_uuid = mcs_media_state_char.uuid; + + //1st desc + btgatt_db_element_t desc1 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc1); + + //media supported feature + btgatt_db_element_t mcs_media_opcode_supported_char = {.uuid = GMCS_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(mcs_media_opcode_supported_char); + mcsServerServiceInfo.media_control_point_opcode_supported_uuid = + mcs_media_opcode_supported_char.uuid; + + //2nd desc + btgatt_db_element_t desc2 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc2); + + ////media player name + btgatt_db_element_t mcs_media_player_name_char = {.uuid = GMCS_MEDIA_PLAYER_NAME_UUID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + mcs_services.push_back(mcs_media_player_name_char); + mcsServerServiceInfo.media_player_name_uuid = mcs_media_player_name_char.uuid; + + //3rd desc + btgatt_db_element_t desc3 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc3); + + //media control point + btgatt_db_element_t mcs_media_control_point_char = {.uuid = GMCS_MEDIA_CONTROL_POINT, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_WRITE_NR| + GATT_CHAR_PROP_BIT_WRITE| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_WRITE}; + + mcs_services.push_back(mcs_media_control_point_char); + + mcsServerServiceInfo.media_player_name_uuid = mcs_media_control_point_char.uuid; + + //4th desc + btgatt_db_element_t desc4 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc4); + + btgatt_db_element_t track_changed_char = {.uuid = GMCS_TRACK_CHANGED, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(track_changed_char); + mcsServerServiceInfo.track_changed_uuid = track_changed_char.uuid; + + //5th desc + btgatt_db_element_t desc5 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc5); + + btgatt_db_element_t track_title_char = {.uuid = GMCS_TRACK_TITLE, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_NOTIFY| + GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(track_title_char); + mcsServerServiceInfo.track_title_uuid = track_title_char.uuid; + + //6th desc + btgatt_db_element_t desc6 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc6); + + + btgatt_db_element_t track_duration_char = {.uuid = GMCS_TRACK_DURATION, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_NOTIFY| + GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(track_duration_char); + mcsServerServiceInfo.track_duration_uuid = track_duration_char.uuid; + + //7th desc + btgatt_db_element_t desc7 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc7); + + btgatt_db_element_t track_position_char = {.uuid = GMCS_TRACK_POSITION, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_WRITE_NR| + GATT_CHAR_PROP_BIT_WRITE| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + + mcs_services.push_back(track_position_char); + mcsServerServiceInfo.track_position_uuid = track_position_char.uuid; + + //8th desc + btgatt_db_element_t desc8 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc8); + + btgatt_db_element_t playing_order_supported_char = {.uuid = GMCS_PLAYING_ORDER_SUPPORTED, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(playing_order_supported_char); + mcsServerServiceInfo.playing_order_supported_uuid = playing_order_supported_char.uuid; + + btgatt_db_element_t playing_order_char = {.uuid = GMCS_PLAYING_ORDER, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_WRITE_NR| + GATT_CHAR_PROP_BIT_WRITE| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + + mcs_services.push_back(playing_order_char); + mcsServerServiceInfo.playing_order_uuid = playing_order_char.uuid; + + //10th desc + btgatt_db_element_t desc10 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc10); + + btgatt_db_element_t ccid_char = {.uuid = GMCS_CONTENT_CONTROLID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(ccid_char); + mcsServerServiceInfo.ccid_uuid = ccid_char.uuid; + + btgatt_db_element_t seek_speed_char = {.uuid = GMCS_SEEKING_SPEED_UUID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + mcs_services.push_back(seek_speed_char); + mcsServerServiceInfo.seeking_speed_uuid = seek_speed_char.uuid; + + return mcs_services; +} + + +static void OnMcpServiceAddedCb(uint8_t status, int serverIf, + std::vector service) { + + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << "%s: Attempt to register restricted service"<< __func__; + return; + } + for(int i = 0; i < (int)service.size(); i++) { + + if (service[i].uuid == GMCS_UUID) { + LOG(INFO) << __func__ << " mcs service added attr handle " << service[i].attribute_handle; + } else if (service[i].uuid == GMCS_MEDIA_STATE_UUID) { + mcsServerServiceInfo.media_state_handle = service[i++].attribute_handle; + mcsServerServiceInfo.media_state_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " media_state_handle" << + mcsServerServiceInfo.media_state_handle; + LOG(INFO) << __func__ << " media_state_handle desc" << + mcsServerServiceInfo.media_state_desc; + } else if(service[i].uuid == GMCS_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED) { + mcsServerServiceInfo.media_control_point_opcode_supported_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_handle register" << + mcsServerServiceInfo.media_control_point_opcode_supported_handle; + mcsServerServiceInfo.media_control_point_opcode_supported_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_handle desc register" << + mcsServerServiceInfo.media_control_point_opcode_supported_desc; + } else if(service[i].uuid == GMCS_MEDIA_PLAYER_NAME_UUID) { + mcsServerServiceInfo.media_player_name_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << " media_player_name_handle GMCS_MEDIA_PLAYER_NAME_UUID register" + << mcsServerServiceInfo.media_player_name_handle; + mcsServerServiceInfo.media_player_name_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " media_player_name_handle GMCS_MEDIA_PLAYER_NAME_UUID desc" + << mcsServerServiceInfo.media_player_name_desc; + } else if(service[i].uuid == GMCS_MEDIA_CONTROL_POINT) { + mcsServerServiceInfo.media_control_point_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_handle GMCS_MEDIA_CONTROL_POINT register" + << mcsServerServiceInfo.media_control_point_handle; + mcsServerServiceInfo.media_control_point_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_handle GMCS_MEDIA_CONTROL_POINT desc" + << mcsServerServiceInfo.media_control_point_desc; + } else if(service[i].uuid == GMCS_TRACK_CHANGED) { + mcsServerServiceInfo.track_changed_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "track_changed_handle GMCS_TRACK_CHANGED register" + << mcsServerServiceInfo.track_changed_handle; + mcsServerServiceInfo.track_changed_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "track_changed_handle GMCS_TRACK_CHANGED desc" + << mcsServerServiceInfo.track_changed_desc; + } else if(service[i].uuid == GMCS_TRACK_TITLE) { + mcsServerServiceInfo.track_title_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "track_title_handle GMCS_TRACK_TITLE register" + << mcsServerServiceInfo.track_title_handle; + mcsServerServiceInfo.track_title_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "track_title_handle GMCS_TRACK_TITLE desc" + << mcsServerServiceInfo.track_title_desc; + } else if(service[i].uuid == GMCS_TRACK_DURATION) { + mcsServerServiceInfo.track_duration_handle = + service[i++].attribute_handle; + + LOG(INFO) << __func__ << "track_duration_handle GMCS_TRACK_DURATION register" + << mcsServerServiceInfo.track_duration_handle; + mcsServerServiceInfo.track_duration_desc = + service[i].attribute_handle; + + LOG(INFO) << __func__ << "track_duration_handle GMCS_TRACK_DURATION desc" + << mcsServerServiceInfo.track_duration_desc; + + } else if(service[i].uuid == GMCS_TRACK_POSITION) { + mcsServerServiceInfo.track_position_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "track_position_handle GMCS_TRACK_POSITION register" + << mcsServerServiceInfo.track_position_handle; + mcsServerServiceInfo.track_position_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "track_position_handle GMCS_TRACK_POSITION desc" + << mcsServerServiceInfo.track_position_handle; + + } else if(service[i].uuid == GMCS_PLAYING_ORDER_SUPPORTED) { + mcsServerServiceInfo.playing_order_supported_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << "playing_order_supported_handle GMCS_PLAYING_ORDER_SUPPORTED register" + << mcsServerServiceInfo.playing_order_supported_handle; + } else if(service[i].uuid == GMCS_PLAYING_ORDER) { + mcsServerServiceInfo.playing_order_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "playing_order_handle GMCS_PLAYING_ORDER register" + << mcsServerServiceInfo.playing_order_handle; + mcsServerServiceInfo.playing_order_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "playing_order_handle GMCS_PLAYING_ORDER desc" + << mcsServerServiceInfo.playing_order_desc; + } else if(service[i].uuid == GMCS_CONTENT_CONTROLID) { + mcsServerServiceInfo.ccid_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << "ccid_handle GMCS_CONTENT_CONTROLID register" << + mcsServerServiceInfo.ccid_handle; + } else if(service[i].uuid == GMCS_SEEKING_SPEED_UUID) { + mcsServerServiceInfo.seeking_speed_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << "ccid_handle GMCS_SEEKING_SPEED_ID register" << + mcsServerServiceInfo.seeking_speed_handle; + } + } //for +} + + +void BTMcpCback(tBTA_GATTS_EVT event, tBTA_GATTS* param) { + HandleMcsEvent((uint32_t)event, param); +} + +//mcs handle event +void HandleMcsEvent(uint32_t event, void* param) { + LOG(INFO) << __func__ << " mcs handle event " << get_mcp_event_name(event); + tBTA_GATTS* p_data = NULL; + uint32_t proc_event; + mcp_resp_t *rsp = (mcp_resp_t *)osi_malloc(sizeof(mcp_resp_t)); + if (rsp == NULL) { + LOG(INFO) << __func__ << " mcs handle return rsp not allocated "; + return; + } + uint8_t status = BT_STATUS_SUCCESS; + proc_event = MCP_NONE_EVENT; + rsp->event = MCP_NONE_EVENT; + switch (event) { + + case MCP_INIT_EVENT: + { + // Uuid app_uuid = (Uuid)*param; + Uuid aap_uuid = Uuid::FromString("1849"); + mediaPlayerInfo.media_state = MCP_STATE_INACTIVE; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_INACTIVE] = + MediaStateInactiveHandler; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_PAUSE] = + MediaStatePauseHandler; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_PLAYING] = + MediaStatePlayingHandler; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_SEEKING] = + MediaStateSeekingHandler; + + mediaPlayerInfo.media_supported_feature = MCP_DEFAULT_MEDIA_CTRL_SUPP_FEAT; + mediaPlayerInfo.ccid = 0; + mediaPlayerInfo.seeking_speed = 0; + mediaPlayerInfo.duration = TRACK_POSITION_UNAVAILABLE; + mediaPlayerInfo.position = TRACK_DURATION_UNAVAILABLE; + mediaPlayerInfo.track_changed = false; + mediaPlayerInfo.playing_order_value = 1; + mediaPlayerInfo.playing_order_supported = 1; + mediaPlayerInfo.player_name_len = 0; + mediaPlayerInfo.track_title_len = 0; + mediaPlayerInfo.media_ctrl_point = 0; + //adding app with random uuid + BTA_GATTS_AppRegister(aap_uuid, BTMcpCback, true); + break; + } + + case MCP_CLEANUP_EVENT: + { + //initiate disconnection to all connected device + //unregister APP + BTA_GATTS_AppDeregister(mcsServerServiceInfo.server_if); + break; + } + case BTA_GATTS_REG_EVT: + { + p_data = (tBTA_GATTS*)param; + if (p_data->reg_oper.status == BT_STATUS_SUCCESS) { + mcsServerServiceInfo.server_if = p_data->reg_oper.server_if; + std::vector service; + service = McpAddService(mcsServerServiceInfo.server_if); + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << __func__ << " service app register uuid is not valid"; + break; + } + LOG(INFO) << __func__ << " service app register"; + BTA_GATTS_AddService(mcsServerServiceInfo.server_if, service, base::Bind(&OnMcpServiceAddedCb)); + } + break; + } + + case BTA_GATTS_DEREG_EVT: + { + break; + } + + case BTA_GATTS_CONF_EVT: { + p_data = (tBTA_GATTS*)param; + uint16_t conn_id = p_data->req_data.conn_id; + uint8_t status = p_data->req_data.status; + LOG(INFO) << __func__ << "conn_id :" << conn_id << "status:" << status; + if (status == BT_STATUS_SUCCESS) { + LOG(INFO) << __func__ << "Notification callback for conn_id :" << conn_id; + GattsOpsQueue::NotificationCallback(conn_id); + } + break; + } + + case BTA_GATTS_CONGEST_EVT: + { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice; + remoteDevice = instance->remoteDevices.FindByConnId(p_data->congest.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id :" + << p_data->congest.conn_id; + break; + } + // rsp->ConngestionOp.status = p_data->req_data.status; + rsp->remoteDevice = remoteDevice; + rsp->oper.CongestionOp.congested = p_data->congest.congested; + proc_event = MCP_CONGESTION_UPDATE; + rsp->event = MCP_CONGESTION_UPDATE; + break; + } + case BTA_GATTS_MTU_EVT: { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice; + remoteDevice = instance->remoteDevices.FindByConnId(p_data->req_data.p_data->mtu); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id :" + << p_data->congest.conn_id; + break; + } + LOG(INFO) << __func__ << " conn_id :"<< p_data->req_data.p_data->mtu; + LOG(INFO) <<"mtu " <req_data.p_data->mtu; + proc_event = MCP_MTU_UPDATE; + rsp->event = MCP_MTU_UPDATE; + rsp->remoteDevice = remoteDevice; + rsp->oper.MtuOp.mtu = p_data->req_data.p_data->mtu; + break; + } + case BTA_GATTS_CONNECT_EVT: { + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " remote devices connected"; + // need to discuss how to get encryption support + /* + #if (!defined(BTA_SKIP_BLE_START_ENCRYPTION) || BTA_SKIP_BLE_START_ENCRYPTION == FALSE) + btif_gatt_check_encrypted_link(p_data->conn.remote_bda, + p_data->conn.transport); + #endif*/ + RemoteDevice remoteDevice; + memset(&remoteDevice, 0, sizeof(remoteDevice)); + if(instance->remoteDevices.FindByAddress(p_data->conn.remote_bda)) { + LOG(INFO) << __func__ << " remote devices already there is connected list"; + status = BT_STATUS_FAIL; + return; + } + remoteDevice.peer_bda = p_data->conn.remote_bda; + remoteDevice.conn_id = p_data->conn.conn_id; + if(instance->remoteDevices.Add(remoteDevice) == false) { + LOG(INFO) << __func__ << " remote device is not added : max connection reached"; + // need to check disconnection required + break; + } + remoteDevice.state = MCP_DISCONNECTED; + + LOG(INFO) << __func__ << " remote devices connected conn_id: "<< remoteDevice.conn_id << + "bd_addr " << remoteDevice.peer_bda; + rsp->remoteDevice = instance->remoteDevices.FindByAddress(p_data->conn.remote_bda); + if ( rsp->remoteDevice == NULL) { + LOG(INFO) << __func__ ; + break; + } + proc_event = MCP_CONNECTION; + rsp->event = MCP_CONNECTION; + break; + } + + case BTA_GATTS_CLOSE_EVT: + case BTA_GATTS_DISCONNECT_EVT: { + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " remote devices disconnected conn_id " << p_data->conn.conn_id; + RemoteDevice *remoteDevice; + remoteDevice = instance->remoteDevices.FindByConnId(p_data->conn.conn_id); + if((!remoteDevice) ) { + status = BT_STATUS_FAIL; + break; + } + + rsp->remoteDevice = remoteDevice; + proc_event = MCP_DISCONNECTION; + rsp->event = MCP_DISCONNECTION; + LOG(INFO) << __func__ << " disconnected conn_id " << p_data->conn.conn_id; + break; + } + + case BTA_GATTS_STOP_EVT: + // not required + break; + + case BTA_GATTS_DELELTE_EVT: + // not required + break; + + case BTA_GATTS_READ_CHARACTERISTIC_EVT: { + p_data = (tBTA_GATTS*)param; + std::vector value; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + LOG(INFO) << __func__ << " charateristcs read handle " << + p_data->req_data.p_data->read_req.handle <<" trans_id : " << + p_data->req_data.trans_id; + + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore read operation"; + status = BT_STATUS_FAIL; + break; + } + + LOG(INFO) <<" offset: " << p_data->req_data.p_data->read_req.offset << + " long : " << p_data->req_data.p_data->read_req.is_long; + + rsp->rsp_value.attr_value.auth_req = 0; + rsp->rsp_value.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp->rsp_value.attr_value.offset = p_data->req_data.p_data->read_req.offset; + + if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_state_handle) { + proc_event = MCP_MEDIA_STATE_READ; + LOG(INFO) << __func__ << " media_state_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_control_point_opcode_supported_handle) { + proc_event = MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_player_name_handle) { + proc_event = MCP_MEDIA_PLAYER_NAME_READ; + LOG(INFO) << __func__ << " media_player_name_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_title_handle) { + proc_event = MCP_TRACK_TITLE_READ; + LOG(INFO) << __func__ << " track_title_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_position_handle) { + proc_event = MCP_TRACK_POSITION_READ; + LOG(INFO) << __func__ << " track_position_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_duration_handle) { + proc_event = MCP_TRACK_DURATION_READ; + LOG(INFO) << __func__ << " track_duration_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.ccid_handle) { + LOG(INFO) << __func__ << " ccid_handle read"; + proc_event = MCP_CCID_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.seeking_speed_handle) { + LOG(INFO) << __func__ << " seeking_speed_handle read"; + proc_event = MCP_SEEKING_SPEED_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.playing_order_supported_handle) { + LOG(INFO) << __func__ << " playing_order_supported_handle read"; + proc_event = MCP_PLAYING_ORDER_SUPPORTED_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.playing_order_handle) { + LOG(INFO) << __func__ << " playing_order_handle read"; + proc_event = MCP_PLAYING_ORDER_READ; + } else { + LOG(INFO) << __func__ << " read request for unknown handle" << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + LOG(INFO) << __func__ << " read request handle" << p_data->req_data.p_data->read_req.handle << + "connection id" << p_data->req_data.conn_id; + rsp->event = MCP_READ_RSP; + rsp->oper.ReadOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadOp.is_long = p_data->req_data.p_data->read_req.is_long; + rsp->oper.ReadOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_READ_DESCRIPTOR_EVT: { + LOG(INFO) << __func__ << " read descriptor"; + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " charateristcs read desc handle " << + p_data->req_data.p_data->read_req.handle << " offset : " + << p_data->req_data.p_data->read_req.offset; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_FAIL; + break; + } + + if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_state_desc) { + LOG(INFO) << __func__ << " media_state_desc read"; + proc_event = MCP_MEDIA_STATE_READ_DESCRIPTOR; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_control_point_desc) { + LOG(INFO) << __func__ << " media_control_point_desc read"; + proc_event = MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_control_point_opcode_supported_desc) { + LOG(INFO) << __func__ << " media_control_point_opcode_supported_desc read"; + proc_event = MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_player_name_desc) { + LOG(INFO) << __func__ << " media_player_name_desc read"; + proc_event = MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_changed_desc) { + LOG(INFO) << __func__ << " track_changed_desc read"; + proc_event = MCP_TRACK_CHANGED_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_title_desc) { + LOG(INFO) << __func__ << " track_title_desc read"; + proc_event = MCP_TRACK_TITLE_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_position_desc) { + LOG(INFO) << __func__ << " track_position_desc read"; + proc_event = MCP_TRACK_POSITION_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_duration_desc) { + LOG(INFO) << __func__ << " track_duration_desc read"; + proc_event = MCP_TRACK_DURATION_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.playing_order_desc) { + LOG(INFO) << __func__ << " playing_order_desc read"; + proc_event = MCP_PLAYING_ORDER_DESCRIPTOR_READ; + } else { + LOG(INFO) << __func__ << " read request for unknown handle" << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + rsp->event = MCP_DESCRIPTOR_READ_RSP; + rsp->rsp_value.attr_value.auth_req = 0; + rsp->rsp_value.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp->rsp_value.attr_value.offset = p_data->req_data.p_data->read_req.offset; + //mcp response + rsp->oper.ReadDescOp.desc_handle = p_data->req_data.p_data->read_req.handle; + rsp->oper.ReadDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadDescOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_WRITE_CHARACTERISTIC_EVT: { + p_data = (tBTA_GATTS*)param; + const auto& req = p_data->req_data.p_data->write_req; + LOG(INFO) << __func__ << " write characteristics len : " << req.len << " value "<< req.value[0]; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_DEVICE_NOT_CONNECTED; + break; + } + + rsp->event = MCP_WRITE_RSP; + rsp->oper.WriteOp.status = BT_STATUS_SUCCESS; + if (req.handle == mcsServerServiceInfo.playing_order_handle) { + proc_event = MCP_PLAYING_ORDER_WRITE; + } else if (req.handle == mcsServerServiceInfo.media_control_point_handle) { + proc_event = MCP_MEDIA_CONTROL_POINT_WRITE; + } else if (req.handle == mcsServerServiceInfo.track_position_handle) { + proc_event = MCP_TRACK_POSITION_WRITE; + } else { + //characteristics handle not matched. + // + rsp->oper.WriteOp.status = BT_STATUS_HANLDE_NOT_MATCHED; + } + rsp->oper.WriteOp.char_handle = req.handle; + rsp->oper.WriteOp.trans_id = p_data->req_data.trans_id; + rsp->remoteDevice = remoteDevice; + rsp->oper.WriteOp.need_rsp = req.need_rsp; + rsp->oper.WriteOp.offset = req.offset; // need to check requirement + memcpy(rsp->oper.WriteOp.data, req.value, req.len); + LOG(INFO) << __func__ << " Local Tx ID " << rsp->oper.WriteOp.trans_id << " Gatt Tx ID: " << p_data->req_data.trans_id; + break; + } + + case BTA_GATTS_WRITE_DESCRIPTOR_EVT: { + p_data = (tBTA_GATTS* )param; + uint16_t req_value = 0; + const auto& req = p_data->req_data.p_data->write_req; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore notification"; + break; + } + req_value = *(uint16_t* )req.value; + //need to initialized with proper error code + int status = BT_STATUS_SUCCESS; + LOG(INFO) << __func__ << " write descriptor :" << req.handle << + "is resp: " << req.need_rsp << "is prep: " << req.is_prep << " value " << req_value; + + if(req.handle == + mcsServerServiceInfo.media_state_desc) { + LOG(INFO) << __func__ << " media_state_desc descriptor write"; + remoteDevice->media_state_notify = req_value; + proc_event = MCP_MEDIA_STATE_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.media_player_name_desc) { + remoteDevice->media_player_name_notify = req_value; + LOG(INFO) << __func__ << " media_player_name_desc descriptor write"; + proc_event = MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.media_control_point_desc) { + remoteDevice->media_control_point_notify = req_value; + LOG(INFO) << __func__ << " media_player_control_point_desc descriptor write"; + proc_event = MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.media_control_point_opcode_supported_desc) { + remoteDevice->media_control_point_opcode_supported_notify = req_value; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_desc descriptor write"; + proc_event = MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_changed_desc) { + remoteDevice->track_changed_notify = req_value; + LOG(INFO) << __func__ << " track_changed_desc descriptor write"; + proc_event = MCP_TRACK_CHANGED_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_title_desc) { + remoteDevice->track_title_notify = req_value; + LOG(INFO) << __func__ << " track_title_desc descriptor write"; + proc_event = MCP_TRACK_TITLE_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_position_desc) { + remoteDevice->track_position_notify = req_value; + LOG(INFO) << __func__ << " track_position_desc descriptor write"; + proc_event = MCP_TRACK_POSITION_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_duration_desc) { + remoteDevice->track_duration_notify = req_value; + LOG(INFO) << __func__ << " track_duration_desc descriptor write"; + proc_event = MCP_TRACK_DURATION_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.playing_order_desc) { + remoteDevice->playing_order_notify = req_value; + LOG(INFO) << __func__ << " playing_order_desc descriptor write"; + proc_event = MCP_PLAYING_ORDER_DESCRIPTOR_WRITE; + } else { + LOG(INFO) << __func__ << " descriptor write not matched "; + status = 4; //need to check error code + } + rsp->event = MCP_DESCRIPTOR_WRITE_RSP; + rsp->oper.WriteDescOp.desc_handle = req.handle; + rsp->oper.WriteDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.WriteDescOp.status = status; + rsp->oper.WriteDescOp.need_rsp = req.need_rsp; + rsp->oper.WriteDescOp.notification = req_value; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_EXEC_WRITE_EVT: { + p_data = (tBTA_GATTS*)param; + // need to check requirement + break; + } + + case BTA_GATTS_PHY_UPDATE_EVT: { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore phy update" + << p_data->phy_update.status; + status = BT_STATUS_FAIL; + break; + } + proc_event = MCP_PHY_UPDATE; + rsp->event = MCP_PHY_UPDATE; + rsp->oper.PhyOp.rx_phy = p_data->phy_update.rx_phy; + rsp->oper.PhyOp.tx_phy = p_data->phy_update.tx_phy; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_CONN_UPDATE_EVT: { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection update device not found"; + break; + } + LOG(INFO) << __func__ << " connection update status" << p_data->phy_update.status; + proc_event = MCP_CONNECTION_UPDATE; + rsp->event = MCP_CONNECTION_UPDATE; + rsp->oper.ConnectionUpdateOp.latency = p_data->conn_update.latency; + rsp->oper.ConnectionUpdateOp.timeout = p_data->conn_update.timeout; + rsp->oper.ConnectionUpdateOp.interval = p_data->conn_update.interval; + rsp->oper.ConnectionUpdateOp.status = p_data->conn_update.status; + rsp->remoteDevice = remoteDevice; + break; + } + + case MCP_ACTIVE_DEVICE_UPDATE: + { + tMCP_SET_ACTIVE_DEVICE *data = (tMCP_SET_ACTIVE_DEVICE *)param; + LOG(INFO) << __func__ << " address " << data->address; + RemoteDevice *remoteDevice = instance->remoteDevices.FindByAddress(data->address); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " active device update device address not found"; + break; + } + instance->remoteDevices.AddSetActiveDevice(data); + rsp->remoteDevice = remoteDevice; + rsp->oper.SetActiveDeviceOp.profile = data->profile; + proc_event = MCP_ACTIVE_DEVICE_UPDATE; + rsp->event = MCP_ACTIVE_DEVICE_UPDATE; + + + break; + } + + case MCP_MEDIA_STATE_UPDATE: + { + tMCP_MEDIA_STATE *data = (tMCP_MEDIA_STATE *) param; + if (mediaPlayerInfo.media_state != data->state) + mediaPlayerInfo.media_state = data->state; + LOG(INFO) << __func__ << " state: " << unsigned(mediaPlayerInfo.media_state); + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_state_handle; + rsp->oper.MediaUpdateOp.data = &mediaPlayerInfo.media_state; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.media_state); + break; + } + + case MCP_MEDIA_PLAYER_NAME_UPDATE: + { + tMCP_MEDIA_PLAYER_NAME *data = (tMCP_MEDIA_PLAYER_NAME *) param; + if (memcmp(mediaPlayerInfo.player_name, data->name, data->len)) { + memcpy(mediaPlayerInfo.player_name, data, data->len); + mediaPlayerInfo.player_name_len = data->len; + } + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_player_name_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)mediaPlayerInfo.player_name; + rsp->oper.MediaUpdateOp.len = mediaPlayerInfo.player_name_len; + break; + } + + case MCP_MEDIA_SUPPORTED_OPCODE_UPDATE: + { + tMCP_MEDIA_OPCODE_SUPPORT *data = (tMCP_MEDIA_OPCODE_SUPPORT *)param; + mediaPlayerInfo.media_supported_feature = data->supported; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.media_supported_feature); + break; + } + + case MCP_MEDIA_CONTROL_POINT_UPDATE: + { + tMCP_MEDIA_CONTROL_POINT *data = (tMCP_MEDIA_CONTROL_POINT *)param; + mediaPlayerInfo.media_ctrl_point = data->req_opcode | (data->result << 8); + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_control_point_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.media_ctrl_point); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_UPDATE: + { + tMCP_PLAYING_ORDER_SUPPORT *data = (tMCP_PLAYING_ORDER_SUPPORT *) param; + mediaPlayerInfo.playing_order_supported = data->order_supported; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = + mcsServerServiceInfo.media_control_point_opcode_supported_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_supported; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.playing_order_supported); + break; + } + + case MCP_PLAYING_ORDER_UPDATE: + { + tMCP_PLAYING_ORDER *data = (tMCP_PLAYING_ORDER *) param; + mediaPlayerInfo.playing_order_value = data->order; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.playing_order_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.playing_order_value); + break; + } + + case MCP_TRACK_CHANGED_UPDATE: + { + tMCP_TRACK_CHANGED *data = (tMCP_TRACK_CHANGED *) param; + mediaPlayerInfo.track_changed = data->status; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.track_changed_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.track_changed); + break; + } + + case MCP_TRACK_POSITION_UPDATE: + { + tMCP_TRACK_POSTION *data = (tMCP_TRACK_POSTION*) param; + if(data->position != mediaPlayerInfo.position) { + mediaPlayerInfo.position = data->position; + } + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.track_position_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.position; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.position); + break; + } + + case MCP_TRACK_DURATION_UPDATE: + { + tMCP_TRACK_DURATION* data = (tMCP_TRACK_DURATION *) param; + mediaPlayerInfo.duration = data->duration; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->rsp_value.handle = mcsServerServiceInfo.track_duration_handle; + rsp->handle = mcsServerServiceInfo.track_duration_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.duration; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.duration); + break; + } + + case MCP_TRACK_TITLE_UPDATE: + { + tMCP_TRACK_TITLE *data = (tMCP_TRACK_TITLE*) param; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.track_title_handle; + if (memcmp(mediaPlayerInfo.title, data->title, data->len)) { + memcpy(mediaPlayerInfo.title, data->title, data->len); + mediaPlayerInfo.track_title_len = data->len; + } + rsp->oper.MediaUpdateOp.data = (uint8_t *)mediaPlayerInfo.title; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.track_title_len); + break; + } + case MCP_CCID_UPDATE: + { + tMCP_CONTENT_CONTROL_ID *data = (tMCP_CONTENT_CONTROL_ID *) param; + mediaPlayerInfo.ccid = (uint8_t)data->ccid; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.ccid_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.ccid; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.ccid); + break; + } + + case MCP_CONNECTION_CLOSE_EVENT: + { + tMCP_CONNECTION_CLOSE* p_data = (tMCP_CONNECTION_CLOSE *)param; + RemoteDevice* remoteDevice = instance->remoteDevices.FindByAddress(p_data->addr); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " address is not in list"; + break; + } + proc_event = MCP_CONNECTION_CLOSE_EVENT; + rsp->remoteDevice = remoteDevice; + break; + } + + case MCP_BOND_STATE_CHANGE_EVENT: + { + tMCP_BOND_STATE_CHANGE* p_data = (tMCP_BOND_STATE_CHANGE *)param; + RemoteDevice* remoteDevice = instance->remoteDevices.FindByAddress(p_data->addr); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " address is not in list"; + break; + } + instance->remoteDevices.Remove(p_data->addr); + break; + } + default: + LOG(INFO) << __func__ << " event not matched !!"; + break; + } + + if(rsp->event != MCP_NONE_EVENT) { + LOG(INFO) << __func__ << " event to media handler " << get_mcp_event_name(rsp->event); + mediaPlayerInfo.MediaStateHandlerPointer[mediaPlayerInfo.media_state](proc_event, rsp, mediaPlayerInfo.media_state); + } + if(rsp) { + LOG(INFO) << __func__ << "free rsp data"; + osi_free(rsp); + } +} + + +bool MediaStateInactiveHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + + mcp_resp_t *p_data = (mcp_resp_t *)param; + + RemoteDevice *device = p_data->remoteDevice; + LOG(INFO) << __func__ << " inactive mcs handle event "<< get_mcp_event_name(p_data->event); + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + break; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + } + break; + } + + case MCP_TRACK_POSITION_WRITE: + case MCP_PLAYING_ORDER_WRITE: { + LOG(INFO) << __func__ << "Ignore other request as player is not active"; + //need to ignore write rsp because no player is active + p_data->rsp_value.attr_value.len = 0; + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + std::vector value; + value.push_back(data); + value.push_back(0x03); // To-Do check ??? + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + break; + } + + case MCP_MEDIA_CONTROL_POINT_WRITE: { + LOG(INFO) << __func__ << "Ignore other request as player is not active ctrl pt write"; + //need to ignore write rsp because no player is active + p_data->rsp_value.attr_value.len = 0; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t status = MCP_MEDIA_PLAYER_INACTIVE; + std::vector value; + bool opcode_support = is_opcode_supported(data); + if (!opcode_support) { + status = MCP_OPCODE_NOT_SUPPORTED; + LOG(INFO) << __func__ << "Sending OPCode Unsupported indication"; + } else { + LOG(INFO) << __func__ << "Sending INACTIVE indication"; + } + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "handle " << p_data->oper.WriteOp.char_handle; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + MCP_DEFAULT_MEDIA_CTRL_SUPP_FEAT; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = (int)mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = (int)mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + (uint16_t)mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + (uint16_t)device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_DISCONNECTION: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + LOG(INFO) << __func__ << " event is not in list"; + break; + } + + return BT_STATUS_SUCCESS; +} + + + +bool MediaStatePlayingHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + mcp_resp_t *p_data = (mcp_resp_t *)param; + RemoteDevice *device = p_data->remoteDevice; + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + } + break; + } + case MCP_PLAYING_ORDER_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + if (mediaPlayerInfo.playing_order_supported & data) { + instance->PlayingOrderChangeReq(data & mediaPlayerInfo.playing_order_value); + } else { + LOG(INFO) << __func__ << " ignore playing_order_handle write feature is not supported"; + break; + } + p_data->rsp_value.attr_value.len = 0; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t opcode_support = is_opcode_supported(data); + LOG(INFO) << __func__ << " data " << data << " Tx ID: " << p_data->oper.WriteOp.trans_id; + if (!opcode_support) { + uint8_t status = MCP_OPCODE_NOT_SUPPORTED; + std::vector value; + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + LOG(INFO) << __func__ << "opcode not supported " << data; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + } + if (data != MCP_MEDIA_CONTROL_OPCODE_PLAY) { + instance->MediaControlPointChangeReq((uint32_t)data, device->peer_bda); + LOG(INFO) << __func__ << "media_control_point_handle write "; + } else { + LOG(INFO) << __func__ << " ignore media_control_point_handle write feature is not supported/already playing"; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + LOG(INFO) << __func__ << " p_data->event "<< p_data->event; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_WRITE: { + uint32_t track_duration = mediaPlayerInfo.duration; + uint32_t track_position = mediaPlayerInfo.position; + uint32_t data; + uint8_t *w_data = p_data->oper.WriteOp.data; + STREAM_TO_UINT32(data, w_data); + uint32_t position = track_duration; + if ((track_position == (uint32_t)data) || (data < 0) || + (track_duration == 0xFFFF) || (track_duration > 0 && track_duration < data)) { + LOG(INFO) << __func__ << " ignore track_position_handle write"; + } else { + position = data; + instance->TrackPositionChangeReq(data); + LOG(INFO) << __func__ << " track_position_handle write"; + } + std::vector value; + value.push_back(position & 0xff); + value.push_back((position >> 8) & 0xff); + value.push_back((position >> 16) & 0xff); + value.push_back((position >> 24) & 0xff); + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + *(uint32_t *)&mediaPlayerInfo.media_supported_feature; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + LOG(INFO) << __func__ << " calling device state" << device->state; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + break; + } + + return BT_STATUS_SUCCESS; +} + +bool MediaStateSeekingHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + mcp_resp_t *p_data = (mcp_resp_t *)param; + RemoteDevice *device = p_data->remoteDevice; + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = + instance->remoteDevices.FindNotifyDevices(p_data->handle); + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + break; + } + std::vector::iterator it; + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + } + + break; + } + + case MCP_PLAYING_ORDER_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + if (mediaPlayerInfo.playing_order_supported & data) { + instance->PlayingOrderChangeReq(data & mediaPlayerInfo.playing_order_value); + } else { + LOG(INFO) << __func__ << " ignore playing_order_handle write feature is not supported"; + break; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t opcode_support = is_opcode_supported(data); + if (!opcode_support) { + uint8_t status = MCP_OPCODE_NOT_SUPPORTED; + std::vector value; + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + LOG(INFO) << __func__ << "opcode not supported " << data; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + } + + if (((data == MCP_MEDIA_CONTROL_OPCODE_PAUSE) || + (data == MCP_MEDIA_CONTROL_OPCODE_PLAY)) && + (instance->remoteDevices.FindActiveDevice(p_data->remoteDevice)) == false) { + LOG(INFO) << __func__ << " media control point write received from inactive device"; + break; + } + if ((data != MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD) && (data != MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND) + && (data != MCP_MEDIA_CONTROL_OPCODE_MOVE_RELATIVE)) { + instance->MediaControlPointChangeReq(data, device->peer_bda); + LOG(INFO) << __func__ << "media_control_point_handle write "; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_WRITE: { + uint32_t track_duration = mediaPlayerInfo.duration; + uint32_t track_position = mediaPlayerInfo.position; + uint32_t data; + uint8_t *w_data = p_data->oper.WriteOp.data; + uint32_t position = track_duration; + STREAM_TO_UINT32(data, w_data); + if ((track_position == (uint32_t)data) || (data < 0) || + (track_duration == 0xFFFF) || (track_duration > 0 && track_duration < data)) { + LOG(INFO) << __func__ << " ignore track_position_handle write"; + } else { + position = data; + instance->TrackPositionChangeReq(data); + LOG(INFO) << __func__ << " track_position_handle write"; + } + std::vector value; + value.push_back(position & 0xff); + value.push_back((position >> 8) & 0xff); + value.push_back((position >> 16) & 0xff); + value.push_back((position >> 24) & 0xff); + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + *(uint32_t *)&mediaPlayerInfo.media_supported_feature; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device MCP_CONGESTION_UPDATE update: " << event; + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + break; + } + return BT_STATUS_SUCCESS; +} + + +bool MediaStatePauseHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + + mcp_resp_t *p_data = (mcp_resp_t *)param; + RemoteDevice *device = p_data->remoteDevice; + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + + } + break; + } + + case MCP_PLAYING_ORDER_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + if (mediaPlayerInfo.playing_order_supported & data) { + instance->PlayingOrderChangeReq(data & mediaPlayerInfo.playing_order_value); + } else { + LOG(INFO) << __func__ << " ignore playing_order_handle write feature is not supported"; + break; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t opcode_support = is_opcode_supported(data); + if (!opcode_support) { + uint8_t status = MCP_OPCODE_NOT_SUPPORTED; + std::vector value; + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + LOG(INFO) << __func__ << "opcode not supported " << data; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + } + + if ((data != MCP_MEDIA_CONTROL_OPCODE_PLAY) && + (instance->remoteDevices.FindActiveDevice(p_data->remoteDevice) == false) + && is_pts_running() == false) { + LOG(INFO) << __func__ << " media control point write received from inactive device"; + break; + } + if (data != MCP_MEDIA_CONTROL_OPCODE_PAUSE) { + // MCP_MEDIA_CONTROL_STOP need to check for stop + instance->MediaControlPointChangeReq(data, device->peer_bda); + LOG(INFO) << __func__ << "media_control_point_handle write "; + } else { + LOG(INFO) << __func__ << " ignore media_control_point_handle write feature is not supported / already paused"; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_WRITE: { + uint32_t track_duration = mediaPlayerInfo.duration; + uint32_t track_position = mediaPlayerInfo.position; + uint32_t data; + uint8_t *w_data = p_data->oper.WriteOp.data; + STREAM_TO_UINT32(data, w_data); + uint32_t position = track_duration; + if ((track_position == (uint32_t)data) || (data < 0) || + (track_duration == 0xFFFF) || (track_duration > 0 && track_duration < data)) { + LOG(INFO) << __func__ << " ignore track_position_handle write"; + } else { + position = data; + instance->TrackPositionChangeReq(data); + LOG(INFO) << __func__ << " track_position_handle write"; + } + std::vector value; + value.push_back(position & 0xff); + value.push_back((position >> 8) & 0xff); + value.push_back((position >> 16) & 0xff); + value.push_back((position >> 24) & 0xff); + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + LOG(INFO) << __func__ << " before player name len: " << len; + LOG(INFO) << __func__ << " before player name mtu: " << device->mtu; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + LOG(INFO) << __func__ << " player name len: " << len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + LOG(INFO) << __func__ << " calling player name read"; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + *(uint32_t *)&mediaPlayerInfo.media_supported_feature; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t*)&mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + break; + } + return BT_STATUS_SUCCESS; +} + +bool DeviceStateConnectionHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " device connected handle " << get_mcp_event_name(event); + mcp_resp_t *p_data = (mcp_resp_t *) param; + switch (event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " device notify all "; + if (is_pts_running() || p_data->remoteDevice->active_profile == 0x10) {// need to check profile value + std::vector value; + tMCP_MEDIA_UPDATE *notfiyUpdate = &p_data->oper.MediaUpdateOp; + value.assign(notfiyUpdate->data, notfiyUpdate->data + notfiyUpdate->len); + GattsOpsQueue::SendNotification(p_data->remoteDevice->conn_id, + p_data->handle, value, false); + } + break; + } + + case MCP_READ_RSP: + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + + break; + + case MCP_DESCRIPTOR_READ_RSP: + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadDescOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + + break; + + case MCP_DESCRIPTOR_WRITE_RSP: { + LOG(INFO) << __func__ << " device MCP_DESCRIPTOR_WRITE_RSP update rsp :" << p_data->oper.WriteDescOp.need_rsp; + tGATTS_RSP rsp_struct; + rsp_struct.attr_value.handle = p_data->rsp_value.attr_value.handle; + rsp_struct.attr_value.offset = p_data->rsp_value.attr_value.offset; + if (p_data->remoteDevice->congested == false && + p_data->oper.WriteDescOp.need_rsp) { + //send rsp to write + LOG(INFO) << __func__ << " gatt send rsp status" << p_data->oper.WriteDescOp.status; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.WriteDescOp.trans_id, + p_data->oper.WriteDescOp.status, &rsp_struct); + } + break; + } + + case MCP_WRITE_RSP: { + LOG(INFO) << __func__ << " device MCP_WRITE_RSP update Tx ID: " << p_data->oper.WriteOp.trans_id; + bool need_rsp = p_data->oper.WriteOp.need_rsp; + if (need_rsp) { + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.WriteOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + } + break; + } + + case MCP_CONNECTION_UPDATE: { + p_data->remoteDevice->latency = p_data->oper.ConnectionUpdateOp.latency; + p_data->remoteDevice->timeout = p_data->oper.ConnectionUpdateOp.timeout; + p_data->remoteDevice->interval = p_data->oper.ConnectionUpdateOp.interval; + p_data->remoteDevice->active_profile = 0x10; + break; + } + + case MCP_ACTIVE_DEVICE_UPDATE: { + p_data->remoteDevice->active_profile = p_data->oper.SetActiveDeviceOp.profile; + break; + } + case MCP_PHY_UPDATE: { + p_data->remoteDevice->rx_phy = p_data->oper.PhyOp.rx_phy; + p_data->remoteDevice->tx_phy = p_data->oper.PhyOp.tx_phy; + break; + } + + case MCP_MTU_UPDATE: { + p_data->remoteDevice->mtu = p_data->oper.MtuOp.mtu; + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_DISCONNECTION: { + LOG(INFO) << __func__ << " device event MCP_DISCONNECTION remove "; + instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + instance->OnConnectionStateChange(MCP_DISCONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case MCP_CONNECTION_CLOSE_EVENT: { + LOG(INFO) << __func__ << " device connection closing"; + // Close active connection + if (p_data->remoteDevice->conn_id != 0) + BTA_GATTS_Close(p_data->remoteDevice->conn_id); + else + BTA_GATTS_CancelOpen(mcsServerServiceInfo.server_if, p_data->remoteDevice->peer_bda, true); + + // Cancel pending background connections + BTA_GATTS_CancelOpen(mcsServerServiceInfo.server_if, p_data->remoteDevice->peer_bda, false); + break; + } + + case MCP_BOND_STATE_CHANGE_EVENT: + LOG(INFO) << __func__ << "Bond state change : "; + instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + break; + + default: + LOG(INFO) << __func__ << " event not matched"; + break; + } + + return BT_STATUS_SUCCESS; +} + + +bool DeviceStateDisconnectedHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " device disconnected handle " << get_mcp_event_name(event); + mcp_resp_t *p_data = (mcp_resp_t *) param; + switch (event) { + case MCP_CONNECTION: { + p_data->remoteDevice->state = MCP_CONNECTED; + p_data->remoteDevice->media_state_notify = 0x00; + p_data->remoteDevice->media_player_name_notify = 0x00; + p_data->remoteDevice->media_control_point_notify = 0x00; + p_data->remoteDevice->track_changed_notify = 0x00; + p_data->remoteDevice->track_duration_notify = 0x00; + p_data->remoteDevice->track_title_notify = 0x00; + p_data->remoteDevice->track_position_notify = 0x00; + p_data->remoteDevice->playing_order_notify = 0x00; + p_data->remoteDevice->congested = false; + + p_data->remoteDevice->timeout = 0; + p_data->remoteDevice->latency = 0; + p_data->remoteDevice->interval = 0; + p_data->remoteDevice->rx_phy = 0; + p_data->remoteDevice->tx_phy = 0; + p_data->remoteDevice->mtu = -1; + instance->OnConnectionStateChange(MCP_CONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_MTU_UPDATE: + case MCP_PHY_UPDATE: + case MCP_READ_RSP: + case MCP_DESCRIPTOR_READ_RSP: + case MCP_WRITE_RSP: + case MCP_DESCRIPTOR_WRITE_RSP: + case MCP_NOTIFY_ALL: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + default: + //ignore event + LOG(INFO) << __func__ << " Ignore event " << get_mcp_event_name(event); + break; + } + return BT_STATUS_SUCCESS; +} + +void McpCongestionUpdate(mcp_resp_t *p_data) { + p_data->remoteDevice->congested = p_data->oper.CongestionOp.congested; + LOG(INFO) << __func__ << ": conn_id: " << p_data->remoteDevice->conn_id + << ", congested: " << p_data->remoteDevice->congested; + + GattsOpsQueue::CongestionCallback(p_data->remoteDevice->conn_id, + p_data->remoteDevice->congested); +} diff --git a/le_audio/system/bt/bta/vcp/bta_vcp_controller.cc b/le_audio/system/bt/bta/vcp/bta_vcp_controller.cc new file mode 100644 index 0000000000000000000000000000000000000000..5eb7d6c56aea93bf37603b7fd69863fd3fd45f16 --- /dev/null +++ b/le_audio/system/bt/bta/vcp/bta_vcp_controller.cc @@ -0,0 +1,1108 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "bt_target.h" +#include "bta_vcp_controller_api.h" +#include "bta_gatt_api.h" +#include "btm_int.h" +#include "device/include/controller.h" +#include "gap_api.h" +#include "gatt_api.h" +#include "gattc_ops_queue.h" +#include "osi/include/properties.h" + +#include +#include +#include +#include +#include +#include + +using base::Closure; +using bluetooth::Uuid; +using bluetooth::bap::GattOpsQueue; +using bluetooth::vcp_controller::ConnectionState; + +// Assigned Numbers for VCS +Uuid VCS_UUID = Uuid::FromString("1844"); +Uuid VCS_VOLUME_STATE_UUID = Uuid::FromString("2B7D"); +Uuid VCS_VOLUME_CONTROL_POINT_UUID = Uuid::FromString("2B7E"); +Uuid VCS_VOLUME_FLAGS_UUID = Uuid::FromString("2B7F"); + +#define VCS_RETRY_SET_ABS_VOL 0x01 +#define VCS_RETRY_SET_MUTE_STATE 0x02 + +// VCS Application Error Code +#define VCS_INVALID_CHANGE_COUNTER 0x80 +#define VCS_OPCODE_NOT_SUPPORTED 0x81 + +void vcp_controller_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +void vcp_controller_encryption_callback(const RawAddress*, tGATT_TRANSPORT, void*, tBTM_STATUS); +const char* vcp_controller_gatt_callback_evt_str(uint8_t event); +const char* vcp_controller_handle_vcs_evt_str(uint8_t event); + +class VcpControllerImpl; +static VcpControllerImpl* instance; + +class RendererDevices { + private: + + public: + void Add(RendererDevice device) { + if (FindByAddress(device.address) != nullptr) return; + devices.push_back(device); + } + + void Remove(const RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->address != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + RendererDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const RendererDevice& device) { + return device.address == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + RendererDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const RendererDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector devices; +}; + +class VcpControllerImpl : public VcpController { + private: + uint8_t gatt_if; + bluetooth::vcp_controller::VcpControllerCallbacks* callbacks; + RendererDevices rendererDevices; + + public: + virtual ~VcpControllerImpl() = default; + + VcpControllerImpl(bluetooth::vcp_controller::VcpControllerCallbacks* callbacks) + : gatt_if(0), + callbacks(callbacks) { + LOG(INFO) << "VcpControllerImpl gattc app register"; + + BTA_GATTC_AppRegister( + vcp_controller_gattc_callback, + base::Bind( + [](uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start Vcp profile - no gatt " + "clients left!"; + return; + } + instance->gatt_if = client_id; + }), true); + } + + void Connect(const RawAddress& address, bool isDirect) override { + LOG(INFO) << __func__ << " " << address << ", isDirect = " << logbool(isDirect); + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (rendererDevice) { + LOG(INFO) << "Device already in connected/connecting state" << address; + return; + } + + rendererDevices.Add(RendererDevice(address)); + rendererDevice = rendererDevices.FindByAddress(address); + if (!rendererDevice) { + LOG(INFO) << "Device address could not be foundL"; + return; + } + rendererDevice->state = BTA_VCP_CONNECTING; + callbacks->OnConnectionState(ConnectionState::CONNECTING, rendererDevice->address); + + if (!isDirect) { + rendererDevice->bg_conn = true; + } + + BTA_GATTC_Open(gatt_if, address, isDirect, GATT_TRANSPORT_LE, false); + } + + void Disconnect(const RawAddress& address) override { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + LOG(INFO) << __func__ << " " << address; + rendererDevice->state = BTA_VCP_DISCONNECTING; + callbacks->OnConnectionState(ConnectionState::DISCONNECTING, rendererDevice->address); + VcpGattClose(rendererDevice); + } + + void SetAbsVolume(const RawAddress& address, uint8_t volume) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + if (rendererDevice->conn_id == 0) { + LOG(INFO) << __func__ << ": GATT is not connected, skip set absolute volume"; + return; + } + + if (rendererDevice->state != BTA_VCP_CONNECTED) { + LOG(INFO) + << __func__ << ": VCP is not connected, skip set absolute volume, state = " + << loghex(rendererDevice->state); + return; + } + // Send the data packet + LOG(INFO) << __func__ << ": Set abs volume. device=" << rendererDevice->address + << ", volume=" << loghex(volume); + + rendererDevice->vcs.pending_volume_setting = volume; + + uint8_t p_buf[256]; + SetAbsVolumeOp set_abs_vol_op; + set_abs_vol_op.op_id = VCS_CONTROL_POINT_OP_SET_ABS_VOL; + set_abs_vol_op.change_counter = rendererDevice->vcs.volume_state.change_counter; + set_abs_vol_op.volume_setting = volume; + + memcpy(p_buf, &set_abs_vol_op , sizeof(set_abs_vol_op)); + std::vector vect_val(p_buf, p_buf + sizeof(set_abs_vol_op)); + + GattOpsQueue::WriteCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_control_point_handle, vect_val, + GATT_WRITE, VcpControllerImpl::OnSetAbsVolumeStatic, nullptr); + } + + void Mute(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + if (rendererDevice->conn_id == 0) { + LOG(INFO) << __func__ << ": GATT is not connected, skip mute"; + return; + } + + if (rendererDevice->state != BTA_VCP_CONNECTED) { + LOG(INFO) + << __func__ << ": VCP is not connected, skip mute, state = " + << loghex(rendererDevice->state); + return; + } + // Send the data packet + LOG(INFO) << __func__ << ": Mute device=" << rendererDevice->address; + + rendererDevice->vcs.pending_mute_setting = VCS_MUTE_STATE; + + uint8_t p_buf[256]; + MuteOp mute_op; + mute_op.op_id = VCS_CONTROL_POINT_OP_MUTE; + mute_op.change_counter = rendererDevice->vcs.volume_state.change_counter; + + memcpy(p_buf, &mute_op , sizeof(mute_op)); + std::vector vect_val(p_buf, p_buf + sizeof(mute_op)); + + GattOpsQueue::WriteCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_control_point_handle, vect_val, + GATT_WRITE, VcpControllerImpl::OnMuteStatic, nullptr); + } + + void Unmute(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(WARNING) << "Device not connected to profile" << address; + return; + } + + if (rendererDevice->conn_id == 0) { + LOG(INFO) << __func__ << ": GATT is not connected, skip unmute."; + return; + } + + if (rendererDevice->state != BTA_VCP_CONNECTED) { + LOG(INFO) + << __func__ << ": VCP is not connected, skip unmute, state = " + << loghex(rendererDevice->state); + return; + } + // Send the data packet + LOG(INFO) << __func__ << ": unmute device=" << rendererDevice->address; + + rendererDevice->vcs.pending_mute_setting = VCS_UNMUTE_STATE; + + uint8_t p_buf[256]; + UnmuteOp unmute_op; + unmute_op.op_id = VCS_CONTROL_POINT_OP_UNMUTE; + unmute_op.change_counter = rendererDevice->vcs.volume_state.change_counter; + + memcpy(p_buf, &unmute_op , sizeof(unmute_op)); + std::vector vect_val(p_buf, p_buf + sizeof(unmute_op)); + + GattOpsQueue::WriteCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_control_point_handle, vect_val, + GATT_WRITE, VcpControllerImpl::OnUnmuteStatic, nullptr); + } + + void VcpGattClose(RendererDevice* rendererDevice) { + LOG(INFO) << __func__ << " " << rendererDevice->address; + + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(gatt_if, rendererDevice->address, false); + rendererDevice->bg_conn = false; + + if (rendererDevice->conn_id) { + GattOpsQueue::Clean(rendererDevice->conn_id); + BTA_GATTC_Close(rendererDevice->conn_id); + } else { + // cancel pending direct connect + BTA_GATTC_CancelOpen(gatt_if, rendererDevice->address, true); + PostDisconnected(rendererDevice); + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBTA_TRANSPORT transport, uint16_t mtu) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + LOG(INFO) << __func__ << ": address=" << address << ", conn_id=" << conn_id; + + if (!rendererDevice) { + LOG(WARNING) << "Closing connection to non volume renderer device, address=" + << address; + BTA_GATTC_Close(conn_id); + return; + } + + if (status != GATT_SUCCESS) { + if (rendererDevice->bg_conn) { + // whitelist connection failed, that's ok. + LOG(INFO) << "bg conn failed, return immediately"; + return; + } + + LOG(INFO) << "Failed to connect to volume renderer device"; + rendererDevices.Remove(address); + callbacks->OnConnectionState(ConnectionState::DISCONNECTED, address); + return; + } + + if (rendererDevice->bg_conn) { + LOG(INFO) << __func__ << ": backgound connection from: address=" << address; + } + + rendererDevice->bg_conn = false; + rendererDevice->conn_id = conn_id; + + /* verify bond */ + uint8_t sec_flag = 0; + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + + LOG(INFO) << __func__ << ": sec_flag =" << loghex(sec_flag); + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + /* if link has been encrypted */ + OnEncryptionComplete(address, true); + return; + } + + if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + /* if bonded and link not encrypted */ + sec_flag = BTM_BLE_SEC_ENCRYPT; + BTM_SetEncryption(address, BTA_TRANSPORT_LE, vcp_controller_encryption_callback, nullptr, + sec_flag); + return; + } + + /* otherwise let it go through */ + OnEncryptionComplete(address, true); + } + + void OnGattDisconnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress remote_bda, + tBTA_GATT_REASON reason) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown device disconnect, conn_id=" + << loghex(conn_id); + return; + } + LOG(INFO) << __func__ << ": conn_id=" << loghex(conn_id) + << ", reason=" << loghex(reason) << ", remote_bda=" << remote_bda; + + PostDisconnected(rendererDevice); + } + + void PostDisconnected(RendererDevice* rendererDevice) { + LOG(INFO) << __func__ << " " << rendererDevice->address; + rendererDevice->state = BTA_VCP_DISCONNECTED; + + if(rendererDevice->vcs.volume_state_handle != 0xFFFF) { + BTIF_TRACE_WARNING("%s: Deregister notifications", __func__); + BTA_GATTC_DeregisterForNotifications(gatt_if, + rendererDevice->address, + rendererDevice->vcs.volume_state_handle); + } + if(rendererDevice->vcs.volume_flags_handle != 0xFFFF) { + BTIF_TRACE_WARNING("%s: Deregister notifications", __func__); + BTA_GATTC_DeregisterForNotifications(gatt_if, + rendererDevice->address, + rendererDevice->vcs.volume_flags_handle); + } + + if (rendererDevice->conn_id) { + GattOpsQueue::Clean(rendererDevice->conn_id); + rendererDevice->conn_id = 0; + } + + callbacks->OnConnectionState(ConnectionState::DISCONNECTED, rendererDevice->address); + rendererDevices.Remove(rendererDevice->address); + } + + void OnEncryptionComplete(const RawAddress& address, bool success) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + LOG(INFO) << __func__ << " " << address; + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown device" << address; + return; + } + + if (!success) { + LOG(ERROR) << "encryption failed"; + BTA_GATTC_Close(rendererDevice->conn_id); + return; + } + + BTA_GATTC_ServiceSearchRequest(rendererDevice->conn_id, &VCS_UUID); + } + + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + LOG(INFO) << __func__; + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << "Service discovery failed"; + VcpGattClose(rendererDevice); + return; + } + + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* service = nullptr; + if (services) { + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) { + /* + LOG(INFO) << "Found UUID_SERVCLASS_GATT_SERVER, handle=" + << loghex(tmp.handle); + const gatt::Service* service_changed_service = &tmp; + FindServerChangedCCCHandle(conn_id, service_changed_service); + */ + } else if (tmp.uuid == VCS_UUID) { + LOG(INFO) << "Found Volume Control service, handle=" << loghex(tmp.handle); + service = &tmp; + } + } + } else { + LOG(ERROR) << "no services found for conn_id: " << conn_id; + return; + } + + if (!service) { + LOG(ERROR) << "No VCS found"; + VcpGattClose(rendererDevice); + return; + } + + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == VCS_VOLUME_STATE_UUID) { + rendererDevice->vcs.volume_state_handle = charac.value_handle; + + rendererDevice->vcs.volume_state_ccc_handle = + FindCccHandle(conn_id, charac.value_handle); + if (!rendererDevice->vcs.volume_state_ccc_handle) { + LOG(ERROR) << __func__ << ": cannot find volume state CCC descriptor"; + continue; + } + + LOG(INFO) << __func__ + << ": vcs volume_state_handle=" << loghex(charac.value_handle) + << ", ccc=" << loghex(rendererDevice->vcs.volume_state_ccc_handle); + } else if (charac.uuid == VCS_VOLUME_FLAGS_UUID) { + rendererDevice->vcs.volume_flags_handle = charac.value_handle; + + rendererDevice->vcs.volume_flags_ccc_handle = + FindCccHandle(conn_id, charac.value_handle); + if (!rendererDevice->vcs.volume_flags_ccc_handle) { + LOG(ERROR) << __func__ << ": cannot find volume flags CCC descriptor"; + continue; + } + + LOG(INFO) << __func__ + << ": vcs volume_flags_handle=" << loghex(charac.value_handle) + << ", ccc=" << loghex(rendererDevice->vcs.volume_flags_ccc_handle); + } else if (charac.uuid == VCS_VOLUME_CONTROL_POINT_UUID) { + // store volume control point! + rendererDevice->vcs.volume_control_point_handle = charac.value_handle; + } else { + LOG(WARNING) << "Unknown characteristic found:" << charac.uuid; + } + } + + LOG(WARNING) << "reading vcs volume_state_handle"; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + + } + + void OnServiceChangeEvent(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + if (!rendererDevice) { + VLOG(2) << "Skipping unknown device" << address; + return; + } + LOG(INFO) << __func__ << ": address=" << address; + rendererDevice->service_changed_rcvd = true; + GattOpsQueue::Clean(rendererDevice->conn_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + if (!rendererDevice) { + VLOG(2) << "Skipping unknown device" << address; + return; + } + if (rendererDevice->service_changed_rcvd) { + BTA_GATTC_ServiceSearchRequest(rendererDevice->conn_id, &VCS_UUID); + } + } + + void OnVolumeStateRead(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status) + << ", renderer device state: " << loghex(rendererDevice->state); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Error reading Volume State for device" << rendererDevice->address; + } else { + uint8_t* p = value; + uint8_t volume_setting; + STREAM_TO_UINT8(volume_setting, p); + rendererDevice->vcs.volume_state.volume_setting = volume_setting; + + uint8_t mute; + STREAM_TO_UINT8(mute, p); + rendererDevice->vcs.volume_state.mute = mute; + + uint8_t change_counter; + STREAM_TO_UINT8(change_counter, p); + rendererDevice->vcs.volume_state.change_counter = change_counter; + } + + HandleVCSEvent(rendererDevice, VCS_VOLUME_STATE_READ_CMPL_EVT, status); + } + + void OnVolumeFlagsRead(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Error reading Volume Flags for device" << rendererDevice->address; + } else { + uint8_t* p = value; + uint8_t volume_flags; + STREAM_TO_UINT8(volume_flags, p); + rendererDevice->vcs.volume_flags = volume_flags; + } + + HandleVCSEvent(rendererDevice, VCS_VOLUME_FLAGS_READ_CMPL_EVT, status); + } + + void OnVolumeStateCCCWrite(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + HandleVCSEvent(rendererDevice, VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT, status); + } + + void OnVolumeFlagsCCCWrite(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + HandleVCSEvent(rendererDevice, VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT, status); + } + + void OnSetAbsVolume(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + // Check for VCS Invalid Change Counter error, it may + // conflict with GATT_NO_RESOURCES error. + if (status == VCS_INVALID_CHANGE_COUNTER || + status == VCS_OPCODE_NOT_SUPPORTED) { + LOG(ERROR) << __func__ << ": Error code: " << status + << " device: " << rendererDevice->address + << " Read Volume State to update change counter"; + + rendererDevice->vcs.retry_cmd |= VCS_RETRY_SET_ABS_VOL; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + } else { + LOG(ERROR) << __func__ << ": Other errors, not retry"; + } + } else { + rendererDevice->vcs.retry_cmd &= ~VCS_RETRY_SET_ABS_VOL; + LOG(INFO) << "Set abs volume success " << rendererDevice->address; + } + } + + void OnMute(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Volume State Write failed" << rendererDevice->address + << "Read Volume State"; + + rendererDevice->vcs.retry_cmd |= VCS_RETRY_SET_MUTE_STATE; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + } else { + LOG(INFO) << "Mute success" << rendererDevice->address; + } + + } + + void OnUnmute(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Volume State Write failed" << rendererDevice->address + << "Read Volume State"; + + rendererDevice->vcs.retry_cmd |= VCS_RETRY_SET_MUTE_STATE; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + } else { + LOG(INFO) << "Unmute success" << rendererDevice->address; + } + } + + void RetryVolumeControlOp(RendererDevice* rendererDevice) { + LOG(INFO) << __func__ << " " << rendererDevice->address; + + if (rendererDevice->vcs.retry_cmd & VCS_RETRY_SET_ABS_VOL) { + rendererDevice->vcs.retry_cmd &= ~VCS_RETRY_SET_ABS_VOL; + SetAbsVolume(rendererDevice->address, rendererDevice->vcs.pending_volume_setting); + } + + if (rendererDevice->vcs.retry_cmd & VCS_RETRY_SET_MUTE_STATE) { + rendererDevice->vcs.retry_cmd &= ~VCS_RETRY_SET_MUTE_STATE; + if (rendererDevice->vcs.pending_mute_setting == VCS_MUTE_STATE) { + Mute(rendererDevice->address); + } else { + Unmute(rendererDevice->address); + } + } + } + + static void OnVolumeStateReadStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnVolumeStateRead(client_id, conn_id, status, handle, len, value, + data); + } + + static void OnVolumeFlagsReadStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnVolumeFlagsRead(client_id, conn_id, status, handle, len, value, + data); + } + + static void OnVolumeStateCCCWriteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnVolumeStateCCCWrite(client_id, conn_id, status, handle, data); + } + + static void OnVolumeFlagsCCCWriteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnVolumeFlagsCCCWrite(client_id, conn_id, status, handle, data); + } + + static void OnSetAbsVolumeStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnSetAbsVolume(client_id, conn_id, status, handle, data); + } + + static void OnMuteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnMute(client_id, conn_id, status, handle, data); + } + + static void OnUnmuteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnUnmute(client_id, conn_id, status, handle, data); + } + + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + RendererDevice* device = rendererDevices.FindByConnId(conn_id); + + if (!device) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + if (handle == device->vcs.volume_state_handle) { + if ( len != sizeof(device->vcs.volume_state)) { + LOG(ERROR) << __func__ << ": Data Length mismatch, len=" << len + << ", expecting " << sizeof(device->vcs.volume_state); + return; + } + + LOG(INFO) << __func__ << " " << device->address << " volume state notification"; + memcpy(&device->vcs.volume_state, value, len); + callbacks->OnVolumeStateChange(device->vcs.volume_state.volume_setting, + device->vcs.volume_state.mute, device->address); + } else if (handle == device->vcs.volume_flags_handle) { + if ( len != sizeof(device->vcs.volume_flags)) { + LOG(ERROR) << __func__ << ": Data Length mismatch, len=" << len + << ", expecting " << sizeof(device->vcs.volume_flags); + return; + } + + LOG(INFO) << __func__ << " " << device->address << " volume flags notification"; + memcpy(&device->vcs.volume_flags, value, len); + callbacks->OnVolumeFlagsChange(device->vcs.volume_flags, device->address); + } else { + LOG(INFO) << __func__ << ": Mismatched handle, " + << loghex(device->vcs.volume_state_handle) + << " or " << loghex(device->vcs.volume_flags_handle) + << "!=" << loghex(handle); + return; + } + } + + void OnCongestionEvent(uint16_t conn_id, bool congested) { + RendererDevice* device = rendererDevices.FindByConnId(conn_id); + if (!device) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id:" << loghex(conn_id) + << ", congested: " << congested; + GattOpsQueue::CongestionCallback(conn_id, congested); + } + + void HandleVCSEvent(RendererDevice* rendererDevice, uint32_t event, tGATT_STATUS status) { + LOG(INFO) << __func__ << " event = " << vcp_controller_handle_vcs_evt_str(event); + + if (status != GATT_SUCCESS) { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + LOG(ERROR) << __func__ << ": Error status while VCP connecting, Close GATT for device: " + << rendererDevice->address; + VcpGattClose(rendererDevice); + return; + } else if (rendererDevice->state == BTA_VCP_CONNECTED) { + LOG(ERROR) << __func__ << ": Error status while VCP is connected for device: " + << rendererDevice->address; + if (rendererDevice->vcs.retry_cmd != 0) { + rendererDevice->vcs.retry_cmd = 0; + } + return; + } else { + LOG(ERROR) << __func__ << ": Error status in disconnected or disconnecting " + << "Igore handle VCS Event for device: " << rendererDevice->address; + return; + } + } + + switch (event) { + case VCS_VOLUME_STATE_READ_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + LOG(WARNING) << "Setup VCP connection, reading vcs volume_flags_handle"; + GattOpsQueue::ReadCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_flags_handle, + VcpControllerImpl::OnVolumeFlagsReadStatic, nullptr); + break; + } else if (rendererDevice->state == BTA_VCP_CONNECTED) { + if (rendererDevice->vcs.retry_cmd != 0) { + RetryVolumeControlOp(rendererDevice); + } + } + break; + } + + case VCS_VOLUME_FLAGS_READ_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + /* Register and enable the Volume State Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + gatt_if, rendererDevice->address, rendererDevice->vcs.volume_state_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + VcpGattClose(rendererDevice); + return; + } + + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_state_ccc_handle, + std::move(value), GATT_WRITE, VcpControllerImpl::OnVolumeStateCCCWriteStatic, + nullptr); + } + break; + } + + case VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + /* Register and enable the Volume State Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + gatt_if, rendererDevice->address, rendererDevice->vcs.volume_flags_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + VcpGattClose(rendererDevice); + return; + } + + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_flags_ccc_handle, + std::move(value), GATT_WRITE, VcpControllerImpl::OnVolumeFlagsCCCWriteStatic, + nullptr); + } + break; + } + + case VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + LOG(INFO) << __func__ << ": VCP Connection Setup complete"; + rendererDevice->state = BTA_VCP_CONNECTED; + callbacks->OnConnectionState(ConnectionState::CONNECTED, rendererDevice->address); + callbacks->OnVolumeFlagsChange(rendererDevice->vcs.volume_flags, + rendererDevice->address); + callbacks->OnVolumeStateChange(rendererDevice->vcs.volume_state.volume_setting, + rendererDevice->vcs.volume_state.mute, rendererDevice->address); + break; + } + break; + } + + default: + LOG(INFO) << __func__ << ": unexpected VCS event"; + break; + } + } + + // Find the handle for the client characteristics configuration of a given + // characteristics + uint16_t FindCccHandle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + LOG(INFO) << __func__ << " " << ", conn_id: " << conn_id << ", char_handle: " << char_handle; + + if (!p_char) { + LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + return 0; + } + + void CleanUp() { + LOG(INFO) << __func__; + BTA_GATTC_AppDeregister(gatt_if); + for (RendererDevice& device : rendererDevices.devices) { + PostDisconnected(&device); + } + + rendererDevices.devices.clear(); + } +}; + +void vcp_controller_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + LOG(INFO) << __func__ << " event = " << vcp_controller_gatt_callback_evt_str(event); + + if (p_data == nullptr) return; + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_OPEN_EVT: { + if (!instance) return; + tBTA_GATTC_OPEN& o = p_data->open; + instance->OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + break; + } + + case BTA_GATTC_CLOSE_EVT: { + if (!instance) return; + tBTA_GATTC_CLOSE& c = p_data->close; + instance->OnGattDisconnected(c.status, c.conn_id, c.client_if, + c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + if (!instance) return; + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: + if (!instance) return; + if (!p_data->notify.is_notify || p_data->notify.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << p_data->notify.is_notify + << ", len=" << p_data->notify.len; + break; + } + instance->OnNotificationEvent(p_data->notify.conn_id, + p_data->notify.handle, p_data->notify.len, + p_data->notify.value); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + if (!instance) return; + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + if (!instance) return; + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + if (!instance) return; + instance->OnServiceDiscDoneEvent(p_data->remote_bda); + break; + + case BTA_GATTC_CONGEST_EVT: + if (!instance) return; + instance->OnCongestionEvent(p_data->congest.conn_id, + p_data->congest.congested); + break; + + case BTA_GATTC_SEARCH_RES_EVT: + case BTA_GATTC_CANCEL_OPEN_EVT: + case BTA_GATTC_CONN_UPDATE_EVT: + + default: + break; + } +} + +void vcp_controller_encryption_callback(const RawAddress* address, + UNUSED_ATTR tGATT_TRANSPORT transport, + UNUSED_ATTR void* data, tBTM_STATUS status) { + if (instance) { + instance->OnEncryptionComplete(*address, + status == BTM_SUCCESS ? true : false); + } +} + +void VcpController::Initialize( + bluetooth::vcp_controller::VcpControllerCallbacks* callbacks) { + LOG(INFO) << __func__ ; + + if (instance) { + LOG(ERROR) << "Already initialized!"; + } + + instance = new VcpControllerImpl(callbacks); +} + +bool VcpController::IsVcpControllerRunning() { return instance; } + +VcpController* VcpController::Get() { + CHECK(instance); + return instance; +}; + +int VcpController::GetDeviceCount() { + if (!instance) { + LOG(INFO) << __func__ << ": Not initialized yet"; + return 0; + } + + return (instance->GetDeviceCount()); +} + +void VcpController::CleanUp() { + VcpControllerImpl* ptr = instance; + instance = nullptr; + + ptr->CleanUp(); + + delete ptr; +}; + +/******************************************************************************* + * Debugging functions + ******************************************************************************/ +#define CASE_RETURN_STR(const) \ + case const: \ + return #const; + +const char* vcp_controller_gatt_callback_evt_str(uint8_t event) { + switch (event) { + CASE_RETURN_STR(BTA_GATTC_DEREG_EVT) + CASE_RETURN_STR(BTA_GATTC_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_CLOSE_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_CMPL_EVT) + CASE_RETURN_STR(BTA_GATTC_NOTIF_EVT) + CASE_RETURN_STR(BTA_GATTC_ENC_CMPL_CB_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_RES_EVT) + CASE_RETURN_STR(BTA_GATTC_CANCEL_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_CHG_EVT) + CASE_RETURN_STR(BTA_GATTC_CONN_UPDATE_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_DISC_DONE_EVT) + CASE_RETURN_STR(BTA_GATTC_CONGEST_EVT) + default: + return (char*)"Unknown GATT Callback Event"; + } +} + +const char* vcp_controller_handle_vcs_evt_str(uint8_t event) { + switch (event) { + CASE_RETURN_STR(VCS_VOLUME_STATE_READ_CMPL_EVT) + CASE_RETURN_STR(VCS_VOLUME_FLAGS_READ_CMPL_EVT) + CASE_RETURN_STR(VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT) + CASE_RETURN_STR(VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT) + default: + return (char*)"Unknown handling VCS Event"; + } +} + diff --git a/le_audio/system/bt/btif/Android.bp b/le_audio/system/bt/btif/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..134d8089bf1be8e4e17e76785ddb00be08bd7c61 --- /dev/null +++ b/le_audio/system/bt/btif/Android.bp @@ -0,0 +1,98 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +cc_defaults { + name: "fluoride_btif_defaults_qti_adva", + defaults: ["fluoride_defaults"], + include_dirs: [ + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/ag", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btcore/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/hci/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/internal_include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/btm", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/udrv/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/vnd/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/utils/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext", + "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/btif/include", + "vendor/qcom/opensource/commonsys-intf/bluetooth/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/device/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btif/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/sys", + "packages/modules/Bluetooth/system/common", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/btif/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include", + "external/libxml2/include", + ], + shared_libs: [ + "libcutils", + "vendor.qti.hardware.bluetooth_audio@2.0", + "vendor.qti.hardware.bluetooth_audio@2.1", + "libcrypto", + "libxml2", + ], + header_libs: ["libbluetooth_headers"], + cflags: [ + "-DBUILDCFG", + "-DADV_AUDIO_FEATURE=1", + ], + required: ["leaudio_configs.xml"], +} + +// BTA static library for target +// ======================================================== +cc_library_static { + name: "libbt-btif_qti_adva", + defaults: ["fluoride_btif_defaults_qti_adva"], + enabled: false, + srcs: [ + "src/bluetooth_adv_audio.cc", + "src/btif_bap_broadcast.cc", + "src/btif_csip.cc", + "src/btif_acm.cc", + "src/btif_pacs_client.cc", + "src/btif_ascs_client.cc", + "src/btif_bap_config.cc", + "src/btif_bap_uclient.cc", + "src/btif_bap_codec_utils.cc", + "src/btif_vmcp.cc", + "src/btif_apm.cc", + "src/btif_vcp_controller.cc", + "src/btif_dm_adv_audio.cc", + "src/btif_acm_source.cc", + "src/btif_mcp.cc", + "src/btif_cc.cc" + ], +} + +// Bluetooth le audio configs xml +// ======================================================== +prebuilt_etc { + name: "leaudio_configs.xml", + src: "leaudio_configs.xml", + sub_dir: "bluetooth", + system_ext_specific: true, +} diff --git a/le_audio/system/bt/btif/include/bluetooth_adv_audio.h b/le_audio/system/bt/btif/include/bluetooth_adv_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..5fcc4100d0db7d1e719673878c76877bfe5ae5b4 --- /dev/null +++ b/le_audio/system/bt/btif/include/bluetooth_adv_audio.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + * + * Filename: bluetooth_adv_audio.h + * + * Description: Main API header file for LEA interfacing + * + ******************************************************************************/ + +#pragma once + +/******************************************************************************* + * TInterface APIs + ******************************************************************************/ + +const void* get_adv_audio_profile_interface(const char* profile_id); +void init_adv_audio_interfaces(); diff --git a/le_audio/system/bt/btif/include/btif_acm.h b/le_audio/system/bt/btif/include/btif_acm.h new file mode 100644 index 0000000000000000000000000000000000000000..c11dcb7ad6ec6cb8f0ea1862e28c7080d7d3bc23 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_acm.h @@ -0,0 +1,250 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#ifndef BTIF_ACM_H +#define BTIF_ACM_H + +#include + +//#include "bta_acm_api.h" +#include "btif_common.h" +#include "bta_bap_uclient_api.h" +#include "bta_pacs_client_api.h" +#include "bta_ascs_client_api.h" + +typedef uint8_t tBTA_ACM_HNDL; +typedef uint8_t tBTIF_ACM_STATUS; + +#define BTA_ACM_MAX_EVT 26 +#define BTA_ACM_NUM_STRS 6 +#define BTA_ACM_NUM_CIGS 239 +//starting setid from 16 onwards as 16 is inavlid +#define BTA_ACM_MIN_NUM_SETID 17 +#define BTA_ACM_MAX_NUM_SETID 255 +#define CONTEXT_TYPE_UNKNOWN 0 +#define CONTEXT_TYPE_MUSIC 1 +#define CONTEXT_TYPE_VOICE 2 +#define CONTEXT_TYPE_MUSIC_VOICE 3 + +#define BTA_ACM_DISCONNECT_EVT 0 +#define BTA_ACM_CONNECT_EVT 1 +#define BTA_ACM_START_EVT 2 +#define BTA_ACM_STOP_EVT 3 +#define BTA_ACM_RECONFIG_EVT 4 +#define BTA_ACM_CONFIG_EVT 5 +#define BTA_ACM_CONN_UPDATE_TIMEOUT_EVT 6 + +#define BTA_ACM_INITIATOR_SERVICE_ID 0xFF +#define ACM_UUID 0xFFFF +#define ACM_TSEP_SNK 1 + +#define SRC 0 +#define SNK 1 + +constexpr uint8_t STREAM_STATE_DISCONNECTED = 0x00; +constexpr uint8_t STREAM_STATE_CONNECTING = 0x01; +constexpr uint8_t STREAM_STATE_CONNECTED = 0x02; +constexpr uint8_t STREAM_STATE_STARTING = 0x03; +constexpr uint8_t STREAM_STATE_STREAMING = 0x04; +constexpr uint8_t STREAM_STATE_STOPPING = 0x05; +constexpr uint8_t STREAM_STATE_DISCONNECTING = 0x06; +constexpr uint8_t STREAM_STATE_RECONFIGURING = 0x07; + +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; + +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::ucast::StreamReconfig; +using bluetooth::bap::ucast::StreamDiscReason; +using bluetooth::bap::ucast::StreamState; +using bluetooth::bap::ucast::QosConfig; + +using bluetooth::bap::pacs::CodecConfig; + +typedef struct { + RawAddress bd_addr; + int contextType; + int profileType; +}tBTIF_ACM_CONN_DISC; + +typedef struct { + RawAddress bd_addr; +}tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO; + +typedef struct { + RawAddress bd_addr; + StreamType stream_type; + CodecConfig codec_config; + uint32_t audio_location; + QosConfig qos_config; + std::vector codecs_selectable; +}tBTA_ACM_CONFIG_INFO; + +typedef struct { + RawAddress bd_addr; + StreamType stream_type; + StreamState stream_state; + StreamDiscReason reason; +}tBTA_ACM_STATE_INFO; + +typedef struct { + RawAddress bd_addr; + bool is_direct; + StreamStateInfo streams_info; +}tBTIF_ACM_CONNECT; + +typedef struct { + RawAddress bd_addr; + StreamStateInfo streams_info; +}tBTIF_ACM_DISCONNECT; + +typedef struct { + RawAddress bd_addr; + StreamStateInfo streams_info; +}tBTIF_ACM_START; + +typedef struct { + RawAddress bd_addr; + StreamStateInfo streams_info; +}tBTIF_ACM_STOP; + +typedef struct { + RawAddress bd_addr; + StreamReconfig streams_info; +}tBTIF_ACM_RECONFIG; + +typedef union { + tBTIF_ACM_CONN_DISC acm_conn_disc; + tBTA_ACM_STATE_INFO state_info; + tBTA_ACM_CONFIG_INFO config_info; + tBTIF_ACM_CONNECT acm_connect; + tBTIF_ACM_DISCONNECT acm_disconnect; + tBTIF_ACM_START acm_start; + tBTIF_ACM_STOP acm_stop; + tBTIF_ACM_RECONFIG acm_reconfig; +}tBTIF_ACM; + +typedef enum { + /* Reuse BTA_ACM_XXX_EVT - No need to redefine them here */ + BTIF_ACM_CONNECT_REQ_EVT = BTA_ACM_MAX_EVT, + BTIF_ACM_DISCONNECT_REQ_EVT, + BTIF_ACM_START_STREAM_REQ_EVT, + BTIF_ACM_STOP_STREAM_REQ_EVT, + BTIF_ACM_SUSPEND_STREAM_REQ_EVT, + BTIF_ACM_RECONFIG_REQ_EVT, +} btif_acm_sm_event_t; + +typedef enum { + BTA_CSIP_NEW_SET_FOUND_EVT = 1, + BTA_CSIP_SET_MEMBER_FOUND_EVT, + BTA_CSIP_CONN_STATE_CHG_EVT, + BTA_CSIP_LOCK_STATUS_CHANGED_EVT, + BTA_CSIP_LOCK_AVAILABLE_EVT, + BTA_CSIP_SET_SIZE_CHANGED, + BTA_CSIP_SET_SIRK_CHANGED, +} btif_csip_sm_event_t; + +/** + * When the local device is ACM source, get the address of the active peer. + */ +RawAddress btif_acm_source_active_peer(void); + +/** + * When the local device is ACM sink, get the address of the active peer. + */ +RawAddress btif_acm_sink_active_peer(void); + +/** + * Start streaming. + */ +void btif_acm_stream_start(void); + +/** + * Stop streaming. + * + * @param peer_address the peer address or RawAddress::kEmpty to stop all peers + */ +void btif_acm_stream_stop(void); + +/** + * Suspend streaming. + */ +void btif_acm_stream_suspend(void); + +/** + * Start offload streaming. + */ +void btif_acm_stream_start_offload(void); + +bool btif_acm_check_if_requested_devices_stopped(void); + +/** + * Get the Stream Endpoint Type of the Active peer. + * + * @return the stream endpoint type: either AVDT_TSEP_SRC or AVDT_TSEP_SNK + */ +uint8_t btif_acm_get_peer_sep(void); + +/** + + * Report ACM Source Codec State for a peer. + * + * @param peer_address the address of the peer to report + * @param codec_config the codec config to report + * @param codecs_local_capabilities the codecs local capabilities to report + * @param codecs_selectable_capabilities the codecs selectable capabilities + * to report + */ +void btif_acm_report_source_codec_state( + const RawAddress& peer_address, + const CodecConfig& codec_config, + const std::vector& codecs_local_capabilities, + const std::vector& + codecs_selectable_capabilities, int contextType); + +/** + * Initialize / shut down the ACM Initiator service. + * + * @param enable true to enable the ACM Source service, false to disable it + * @return BT_STATUS_SUCCESS on success, BT_STATUS_FAIL otherwise + */ +bt_status_t btif_acm_initiator_execute_service(bool enable); + +/** + * Dump debug-related information for the BTIF ACM module. + * + * @param fd the file descriptor to use for writing the ASCII formatted + * information + */ +void btif_debug_acm_dump(int fd); + +bool btif_acm_is_active(); +uint16_t btif_acm_get_sample_rate(); +uint8_t btif_acm_get_ch_mode(); +uint32_t btif_acm_get_bitrate(); +uint32_t btif_acm_get_octets(uint32_t bit_rate); +uint16_t btif_acm_get_framelength(); +uint8_t btif_acm_get_ch_count(); +uint16_t btif_acm_get_current_active_profile(); +bool btif_acm_is_codec_type_lc3q(); +uint8_t btif_acm_lc3q_ver(); +bool btif_acm_is_call_active(void); + +#endif /* BTIF_ACM_H */ diff --git a/le_audio/system/bt/btif/include/btif_acm_source.h b/le_audio/system/bt/btif/include/btif_acm_source.h new file mode 100644 index 0000000000000000000000000000000000000000..8203a6578afefde992d9711aba6b846fc7b842f7 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_acm_source.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#if AHIM_ENABLED +void btif_register_cb(); +void btif_acm_process_request(tA2DP_CTRL_CMD cmd); +void btif_acm_handle_event(uint16_t event, char* p_param); +bool btif_acm_source_start_session(const RawAddress& peer_address); +bool btif_acm_source_end_session(const RawAddress& peer_address); +bool btif_acm_source_restart_session(const RawAddress& old_peer_address, + const RawAddress& new_peer_address); +void btif_acm_source_command_ack(tA2DP_CTRL_CMD cmd, tA2DP_CTRL_ACK status); +void btif_acm_source_on_stopped(tA2DP_CTRL_ACK status); +void btif_acm_source_on_suspended(tA2DP_CTRL_ACK status); +bool btif_acm_on_started(tA2DP_CTRL_ACK status); +bt_status_t btif_acm_source_setup_codec(); +bool btif_acm_update_sink_latency_change(uint16_t sink_latency); + +#endif diff --git a/le_audio/system/bt/btif/include/btif_bap_broadcast.h b/le_audio/system/bt/btif/include/btif_bap_broadcast.h new file mode 100644 index 0000000000000000000000000000000000000000..f2bf96dc46a1047efbe91bfe560b22199b3fa116 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_bap_broadcast.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + * + * Filename: btif_bap_broadcast.h + * + * Description: Main API header file for all BTIF BAP Broadcast functions + * accessed from internal stack. + * + ******************************************************************************/ + +#ifndef BTIF_BAP_BROADCAST_H +#define BTIF_BAP_BROADCAST_H + +#include "bta_av_api.h" +#include "btif_common.h" +#include "btif_sm.h" + + +/******************************************************************************* + * Type definitions for callback functions + ******************************************************************************/ + +typedef enum { + /* Reuse BTA_AV_XXX_EVT - No need to redefine them here */ + BTIF_BAP_BROADCAST_ENABLE_EVT, + BTIF_BAP_BROADCAST_DISABLE_EVT, + BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT, + BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT, + BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT, + BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT, + BTIF_BAP_BROADCAST_CLEANUP_REQ_EVT, + BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT, + BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT, + BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT, + BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT, + BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT, + BTIF_BAP_BROADCAST_BISES_SETUP_EVT, + BTIF_BAP_BROADCAST_BISES_REMOVE_EVT, + BTIF_BAP_BROADCAST_BIG_SETUP_EVT, + BTIF_BAP_BROADCAST_BIG_REMOVED_EVT, + BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT, + BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT, +} btif_bap_broadcast_sm_event_t; + +enum { + BTBAP_CODEC_CHANNEL_MODE_JOINT_STEREO = 0x01 << 2, + BTBAP_CODEC_CHANNEL_MODE_DUAL_MONO = 0x1 << 3 +}; +/******************************************************************************* + * BTIF AV API + ******************************************************************************/ +bool btif_bap_broadcast_is_active(); + +uint16_t btif_bap_broadcast_get_sample_rate(); +uint8_t btif_bap_broadcast_get_ch_mode(); +uint16_t btif_bap_broadcast_get_framelength(); +uint32_t btif_bap_broadcast_get_mtu(uint32_t bitrate); +uint32_t btif_bap_broadcast_get_bitrate(); +uint8_t btif_bap_broadcast_get_ch_count(); +bool btif_bap_broadcast_is_simulcast_enabled(); +/******************************************************************************* + * + * Function btif_dispatch_sm_event + * + * Description Send event to AV statemachine + * + * Returns None + * + ******************************************************************************/ + +/* used to pass events to AV statemachine from other tasks */ +void btif_bap_ba_dispatch_sm_event(btif_bap_broadcast_sm_event_t event, void *p_data, int len); + + +#endif /* BTIF_BAP_BROADCAST_H */ + diff --git a/le_audio/system/bt/btif/include/btif_bap_codec_utils.h b/le_audio/system/bt/btif/include/btif_bap_codec_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..e557268dc359e2952a268a66cd40cd98084c4ae8 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_bap_codec_utils.h @@ -0,0 +1,90 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include + +#include +#include "bt_types.h" + +using bluetooth::bap::pacs::CodecConfig; + +bool UpdateCapaSupFrameDurations(CodecConfig *config , uint8_t sup_frame); + +bool UpdateCapaMaxSupLc3Frames(CodecConfig *config, + uint8_t max_sup_lc3_frames); + + +bool UpdateCapaPreferredContexts(CodecConfig *config, uint16_t contexts); + + +bool UpdateCapaSupOctsPerFrame(CodecConfig *config, + uint32_t octs_per_frame); + +bool UpdateCapaVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref); + +bool UpdateCapaVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver); + +uint8_t GetCapaSupFrameDurations(CodecConfig *config); + +uint8_t GetCapaMaxSupLc3Frames(CodecConfig *config); + +uint16_t GetCapaPreferredContexts(CodecConfig *config); + +uint32_t GetCapaSupOctsPerFrame(CodecConfig *config); + +bool GetCapaVendorMetaDataLc3QPref(CodecConfig *config); + +uint8_t GetCapaVendorMetaDataLc3QVer(CodecConfig *config); + +// configurations +bool UpdateFrameDuration(CodecConfig *config , uint8_t frame_dur); + +bool UpdateLc3BlocksPerSdu(CodecConfig *config, + uint8_t lc3_blocks_per_sdu) ; + +bool UpdateOctsPerFrame(CodecConfig *config , uint16_t octs_per_frame); + +bool UpdateLc3QPreference(CodecConfig *config , bool lc3q_pref); + +bool UpdateVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref); + +bool UpdateVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver); + +bool UpdatePreferredAudioContext(CodecConfig *config , + uint16_t pref_audio_context); + +uint8_t GetFrameDuration(CodecConfig *config); + +uint8_t GetLc3BlocksPerSdu(CodecConfig *config); + +uint16_t GetOctsPerFrame(CodecConfig *config); + +uint8_t GetLc3QPreference(CodecConfig *config); + +uint8_t GetVendorMetaDataLc3QPref(CodecConfig *config); + +uint8_t GetVendorMetaDataLc3QVer(CodecConfig *config); + +uint16_t GetPreferredAudioContext(CodecConfig *config); + +bool IsCodecConfigEqual(CodecConfig *src_config, CodecConfig *dst_config); + + diff --git a/le_audio/system/bt/btif/include/btif_bap_config.h b/le_audio/system/bt/btif/include/btif_bap_config.h new file mode 100644 index 0000000000000000000000000000000000000000..a83b8e9bb5cf54de177249e100aeacce0e1a020c --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_bap_config.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright (C) 2014 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include +#include + +#include +#include "bt_types.h" + +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::CodecDirection; +using bluetooth::bap::pacs::CodecIndex; + +typedef enum { + REC_TYPE_CAPABILITY = 0x01, + REC_TYPE_CONFIGURATION +} btif_bap_record_type_t; + +const char BTIF_BAP_CONFIG_MODULE[] = "btif_bap_config_module"; + +typedef struct btif_bap_config_section_iter_t btif_bap_config_section_iter_t; + +bool btif_bap_add_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record); + +bool btif_bap_remove_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record); + +bool btif_bap_remove_record_by_context(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction); + +bool btif_bap_remove_all_records(const RawAddress& bd_addr); + +bool btif_bap_get_records(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + std::vector *pac_records); + +bool btif_bap_add_audio_loc(const RawAddress& bd_addr, + CodecDirection direction, uint32_t audio_loc); + +bool btif_bap_rem_audio_loc(const RawAddress& bd_addr, + CodecDirection direction); + +bool btif_bap_add_supp_contexts(const RawAddress& bd_addr, + uint32_t supp_contexts); + +bool btif_bap_get_supp_contexts(const RawAddress& bd_addr, + uint32_t *supp_contexts); + +bool btif_bap_rem_supp_contexts(const RawAddress& bd_addr); + +bool btif_bap_config_clear(void); diff --git a/le_audio/system/bt/btif/include/btif_dm_adv_audio.h b/le_audio/system/bt/btif/include/btif_dm_adv_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..d28863311e2aa5f27a57111bd6d4e87b7daf64c4 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_dm_adv_audio.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#ifndef BTIF_DM_ADV_AUDIO_H +#define BTIF_DM_ADV_AUDIO_H + +extern std::unordered_map adv_audio_device_db; +extern tBTA_TRANSPORT btif_dm_get_adv_audio_transport(const RawAddress& bd_addr); +extern void btif_dm_lea_search_services_evt(uint16_t event, char* p_param); +extern void btif_register_uuid_srvc_disc(bluetooth::Uuid uuid); +extern bool check_adv_audio_cod(uint32_t cod); +extern bool is_remote_support_adv_audio(const RawAddress p_addr); +extern void bte_dm_adv_audio_search_services_evt(tBTA_DM_SEARCH_EVT event, + tBTA_DM_SEARCH* p_data); +extern void btif_dm_release_action_uuid(RawAddress bd_addr); + +#endif + diff --git a/le_audio/system/bt/btif/include/btif_vmcp.h b/le_audio/system/bt/btif/include/btif_vmcp.h new file mode 100644 index 0000000000000000000000000000000000000000..7eb72bf5c8a6494f2b0878e4cb6efc82700de3ea --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_vmcp.h @@ -0,0 +1,147 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include +#include "raw_address.h" +#include "hardware/bt_pacs_client.h" + +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecFrameDuration; +using bluetooth::bap::pacs::CodecConfig; + +#define BAP 0x01 +#define GCP 0x02 +#define WMCP 0x04 +#define VMCP 0x08 +#define BAP_CALL 0x10 +#define GCP_TX_RX 0x20 + +#define EB_CONFIG 1 +#define STEREO_HS_CONFIG_1 2 +#define STEREO_HS_CONFIG_2 3 + +#define VOICE_CONTEXT 1 +#define MEDIA_CONTEXT 2 +#define MEDIA_LL_CONTEXT 3 +#define MEDIA_HR_CONTEXT 4 + +#define SAMPLE_RATE_8000 8000 +#define SAMPLE_RATE_16000 16000 +#define SAMPLE_RATE_24000 24000 +#define SAMPLE_RATE_32000 32000 +#define SAMPLE_RATE_44100 44100 +#define SAMPLE_RATE_48000 48000 + + +#define FRM_DURATION_7_5_MS 7.5 +#define FRM_DURATION_10_MS 10 + +#define OCT_PER_CODEC_FRM_26 26 +#define OCT_PER_CODEC_FRM_30 30 +#define OCT_PER_CODEC_FRM_60 60 +#define OCT_PER_CODEC_FRM_75 75 +#define OCT_PER_CODEC_FRM_80 80 +#define OCT_PER_CODEC_FRM_90 90 +#define OCT_PER_CODEC_FRM_98 98 +#define OCT_PER_CODEC_FRM_100 100 +#define OCT_PER_CODEC_FRM_117 117 +#define OCT_PER_CODEC_FRM_120 120 +#define OCT_PER_CODEC_FRM_130 130 +#define OCT_PER_CODEC_FRM_150 150 +#define OCT_PER_CODEC_FRM_155 155 + +#define UNFRAMED 0 +#define FRAMED 1 + +#define RETRANS_NO_2 2 +#define RETRANS_NO_5 5 +#define RETRANS_NO_7 7 +#define RETRANS_NO_11 11 +#define RETRANS_NO_13 13 +#define RETRANS_NO_23 23 +#define RETRANS_NO_29 29 +#define RETRANS_NO_35 35 +#define RETRANS_NO_41 41 +#define RETRANS_NO_47 47 +#define RETRANS_NO_53 53 + +#define MAX_TRANS_LAT_MS_8 8 +#define MAX_TRANS_LAT_MS_10 10 +#define MAX_TRANS_LAT_MS_15 15 +#define MAX_TRANS_LAT_MS_20 20 +#define MAX_TRANS_LAT_MS_24 24 +#define MAX_TRANS_LAT_MS_25 25 +#define MAX_TRANS_LAT_MS_31 31 +#define MAX_TRANS_LAT_MS_33 33 +#define MAX_TRANS_LAT_MS_45 45 +#define MAX_TRANS_LAT_MS_54 54 +#define MAX_TRANS_LAT_MS_60 60 +#define MAX_TRANS_LAT_MS_71 71 +#define MAX_TRANS_LAT_MS_95 95 + +#define PRES_DELAY_20_MS 20 +#define PRES_DELAY_25_MS 25 +#define PRES_DELAY_40_MS 40 + +#define LEAUDIO_CONFIG_PATH "/system_ext/etc/bluetooth/leaudio_configs.xml" +using namespace std; + +// for storing codec config as it is read from xml +struct codec_config +{ + uint32_t freq_in_hz; + float frame_dur_msecs; + uint8_t oct_per_codec_frm; + uint8_t mandatory; +}; + +// for storing QoS settings as it is read from xml +struct qos_config +{ + uint32_t freq_in_hz; + uint32_t sdu_int_micro_secs; + uint8_t framing; + uint8_t max_sdu_size; + uint8_t retrans_num; + uint8_t max_trans_lat; + uint32_t presentation_delay; + uint8_t mandatory; +}; + + +// QoS configuration in the structure needed by ACM +struct QoSConfig +{ + CodecSampleRate sample_rate; + uint32_t sdu_int_micro_secs; + uint8_t framing; + uint8_t max_sdu_size; + uint8_t retrans_num; + uint8_t max_trans_lat; + uint32_t presentation_delay; + uint8_t mandatory; +}; +void btif_vmcp_init(); + +vector get_all_codec_configs(uint8_t profile, uint8_t context); +vector get_preferred_codec_configs(uint8_t profile, uint8_t context); + +vector get_all_qos_params(uint8_t profile, uint8_t context); +vector get_qos_params_for_codec(uint8_t profile, uint8_t context, CodecSampleRate freq, uint8_t frame_dur, uint8_t octets); diff --git a/le_audio/system/bt/btif/leaudio_configs.xml b/le_audio/system/bt/btif/leaudio_configs.xml new file mode 100644 index 0000000000000000000000000000000000000000..0971d6fbbcbbbee86f0f9f3dc469cb9a61eb09ef --- /dev/null +++ b/le_audio/system/bt/btif/leaudio_configs.xml @@ -0,0 +1,727 @@ + + + + + + + + 8000 + 7500 + 26 + 0 + + + 8000 + 10000 + 30 + 0 + + + 32000 + 7500 + 60 + 0 + + + 32000 + 10000 + 80 + 1 + + + + + + 48000 + 7500 + 75 + 0 + + + 48000 + 10000 + 100 + 1 + + + 48000 + 7500 + 90 + 0 + + + 48000 + 10000 + 120 + 0 + + + 48000 + 7500 + 117 + 0 + + + 48000 + 10000 + 155 + 1 + + + + + + 8000 + 7500 + 0 + 26 + 2 + 8 + 20000 + 0 + + + 8000 + 10000 + 0 + 30 + 2 + 10 + 20000 + 0 + + + 32000 + 7500 + 0 + 60 + 2 + 8 + 20000 + 1 + + + 32000 + 10000 + 0 + 80 + 2 + 10 + 20000 + 1 + + + + + 48000 + 7500 + 0 + 75 + 5 + 15 + 20000 + 0 + + + 48000 + 10000 + 0 + 100 + 5 + 20 + 20000 + 1 + + + 48000 + 7500 + 0 + 90 + 5 + 15 + 20000 + 0 + + + 48000 + 10000 + 0 + 120 + 5 + 20 + 20000 + 0 + + + 48000 + 7500 + 0 + 117 + 5 + 15 + 20000 + 0 + + + 48000 + 10000 + 0 + 155 + 5 + 20 + 20000 + 0 + + + + + 48000 + 7500 + 0 + 75 + 23 + 45 + 20000 + 0 + + + 48000 + 10000 + 0 + 100 + 23 + 60 + 20000 + 1 + + + 48000 + 7500 + 0 + 90 + 23 + 45 + 20000 + 0 + + + 48000 + 10000 + 0 + 120 + 23 + 60 + 20000 + 0 + + + 48000 + 7500 + 0 + 117 + 23 + 45 + 20000 + 0 + + + 48000 + 10000 + 0 + 155 + 23 + 60 + 20000 + 0 + + + + + + + + 32000 + 10000 + 80 + 0 + + + + + 48000 + 10000 + 100 + 0 + + + + + 32000 + 10000 + 0 + 80 + 2 + 10 + 40000 + 0 + + + + + 8000 + 7500 + 0 + 26 + 2 + 8 + 40000 + 0 + + + 8000 + 10000 + 0 + 30 + 2 + 10 + 40000 + 0 + + + 16000 + 7500 + 0 + 30 + 2 + 8 + 40000 + 0 + + + 16000 + 10000 + 0 + 40 + 2 + 10 + 40000 + 0 + + + 24000 + 7500 + 0 + 45 + 2 + 8 + 40000 + 0 + + + 24000 + 10000 + 0 + 60 + 2 + 10 + 40000 + 0 + + + 48000 + 7500 + 0 + 75 + 5 + 15 + 40000 + 0 + + + 48000 + 10000 + 0 + 100 + 15 + 70 + 40000 + 0 + + + 48000 + 7500 + 0 + 90 + 5 + 15 + 40000 + 0 + + + 48000 + 10000 + 0 + 120 + 5 + 20 + 40000 + 0 + + + 32000 + 7500 + 0 + 60 + 2 + 8 + 40000 + 0 + + + 32000 + 10000 + 0 + 80 + 2 + 10 + 40000 + 0 + + + 48000 + 7500 + 0 + 117 + 5 + 15 + 40000 + 0 + + + 48000 + 10000 + 0 + 155 + 13 + 100 + 40000 + 0 + + + + + 8000 + 7500 + 0 + 26 + 41 + 45 + 40000 + 0 + + + 8000 + 10000 + 0 + 30 + 53 + 60 + 40000 + 0 + + + 16000 + 7500 + 0 + 30 + 41 + 45 + 40000 + 0 + + + 16000 + 10000 + 0 + 40 + 47 + 60 + 40000 + 1 + + + 24000 + 7500 + 0 + 45 + 35 + 45 + 40000 + 0 + + + 24000 + 10000 + 0 + 60 + 41 + 60 + 40000 + 0 + + + 32000 + 7500 + 0 + 60 + 29 + 45 + 40000 + 0 + + + 32000 + 10000 + 0 + 80 + 35 + 60 + 40000 + 0 + + + 48000 + 7500 + 0 + 75 + 23 + 45 + 40000 + 0 + + + 48000 + 10000 + 0 + 100 + 23 + 60 + 40000 + 0 + + + 48000 + 7500 + 0 + 90 + 23 + 45 + 40000 + 0 + + + 48000 + 10000 + 0 + 120 + 23 + 60 + 40000 + 0 + + + 48000 + 7500 + 0 + 117 + 23 + 45 + 40000 + 0 + + + 48000 + 10000 + 0 + 155 + 13 + 100 + 40000 + 0 + + + + + + + + 16000 + 7500 + 60 + 1 + + + 16000 + 7500 + 60 + 0 + + + 16000 + 7500 + 30 + 1 + + + + + + 48000 + 7500 + 75 + 0 + + + + + + 16000 + 15000 + 0 + 60 + 2 + 8 + 25000 + 1 + + + 16000 + 15000 + 0 + 60 + 7 + 25 + 25000 + 0 + + + 16000 + 7500 + 0 + 30 + 11 + 33 + 25000 + 1 + + + + + + 48000 + 7500 + 0 + 75 + 5 + 12 + 25000 + 0 + + + + + + + + 48000 + 10000 + 100 + 0 + + + + + 16000 + 10000 + 0 + 40 + 13 + 95 + 40000 + 0 + + + 16000 + 7500 + 0 + 30 + 13 + 75 + 40000 + 0 + + + 32000 + 10000 + 0 + 80 + 13 + 95 + 40000 + 0 + + + 32000 + 7500 + 0 + 60 + 13 + 75 + 40000 + 0 + + + 48000 + 10000 + 0 + 100 + 11 + 40 + 25000 + 0 + + + 48000 + 7500 + 0 + 75 + 13 + 75 + 40000 + 0 + + + + + + diff --git a/le_audio/system/bt/btif/src/bluetooth_adv_audio.cc b/le_audio/system/bt/btif/src/bluetooth_adv_audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..ad1b1accff39f8fc5875ce4e8bbea0e9add4cc9f --- /dev/null +++ b/le_audio/system/bt/btif/src/bluetooth_adv_audio.cc @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright (C) 2009-2012 Broadcom Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +/******************************************************************************* + * + * Filename: bluetooth_adv_audio.cc + * + * Description: Bluetooth LEA HAL implementation + * + ******************************************************************************/ + +#define LOG_TAG "bt_btif_adv_audio" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "osi/include/log.h" +#include "btif_bap_config.h" +#include "bta_csip_api.h" +#include "stack_interface.h" +#include "btcore/include/module.h" +#include "btcore/include/osi_module.h" +#include + +/******************************************************************************* + * Externs + ******************************************************************************/ + +/* list all extended interfaces here */ +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::ascs::AscsClientInterface; +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::vcp_controller::VcpControllerInterface; +using bluetooth::mcp_server::McpServerInterface; +using bluetooth::call_control::CallControllerInterface; +extern PacsClientInterface *btif_pacs_client_get_interface(); +extern AscsClientInterface *btif_ascs_client_get_interface(); +extern UcastClientInterface *btif_bap_uclient_get_interface(); +extern bt_apm_interface_t *btif_apm_get_interface(); +extern btacm_initiator_interface_t* btif_acm_initiator_get_interface(); +extern btbap_broadcast_interface_t * btif_bap_broadcast_get_interface(); +/* Coordinated set identification profile - client */ +extern btcsip_interface_t* btif_csip_get_interface(); +/*Vcp Controller*/ +extern VcpControllerInterface* btif_vcp_get_controller_interface(); +/*Mcp server*/ +extern McpServerInterface* btif_mcp_server_get_interface(); +extern CallControllerInterface* btif_cc_server_get_interface(); + +/******************************************************************************* + * Functions + ******************************************************************************/ + +static bool is_profile(const char* p1, const char* p2) { + CHECK(p1); + CHECK(p2); + return strlen(p1) == strlen(p2) && strncmp(p1, p2, strlen(p2)) == 0; +} + +/***************************************************************************** + * + * BLUETOOTH LEA HAL INTERFACE FUNCTIONS + * + ****************************************************************************/ + +StackCallbacks *stack_callbacks; + +const void* get_adv_audio_profile_interface(const char* profile_id) { + LOG_INFO(LOG_TAG, "%s: id = %s", __func__, profile_id); + + if (is_profile(profile_id, BT_PROFILE_PACS_CLIENT_ID)) { + return btif_pacs_client_get_interface(); + } + + if (is_profile(profile_id, BT_APM_MODULE_ID)) { + return btif_apm_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_ACM_ID)) { + return btif_acm_initiator_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_BAP_BROADCAST_ID)) + return btif_bap_broadcast_get_interface(); + + if (is_profile(profile_id, BT_PROFILE_CSIP_CLIENT_ID)) { + return btif_csip_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_VOLUME_CONTROL_ID)) { + return btif_vcp_get_controller_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_MCP_ID)) { + return btif_mcp_server_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_CC_ID)) { + return btif_cc_server_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_ASCS_CLIENT_ID)) { + return btif_ascs_client_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_BAP_UCLIENT_ID)) { + return bluetooth::bap::ucast::btif_bap_uclient_get_interface(); + } + return NULL; +} + +class StackCallbacksImpl : public StackCallbacks { + public: + ~StackCallbacksImpl() = default; + void OnDevUnPaired(const RawAddress& address) override { + BTA_CsipRemoveUnpairedSetMember(address); + btif_bap_remove_all_records(address); + } + + void OnConfigCleared(void) override { + btif_bap_config_clear(); + } + + void OnStackState(StackState state) { + switch(state) { + case StackState::INITIALIZING: + module_init(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + case StackState::TURNING_ON: + module_start_up(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + case StackState::TURNING_OFF: + module_shut_down(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + case StackState::CLEAND_UP: + module_clean_up(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + default: + break; + } + } +}; + +void init_adv_audio_interfaces() { + stack_callbacks = new StackCallbacksImpl; + StackInterface::Initialize(stack_callbacks); +} diff --git a/le_audio/system/bt/btif/src/btif_acm.cc b/le_audio/system/bt/btif/src/btif_acm.cc new file mode 100644 index 0000000000000000000000000000000000000000..0bc01247e24f77e5d2abffe257fd11511f5a10a3 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_acm.cc @@ -0,0 +1,6672 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#define LOG_TAG "btif_acm" +#include "btif_acm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bta_closure_api.h" +#include "btif_storage.h" +#include +#include +#include "audio_hal_interface/a2dp_encoding.h" +#include "bt_common.h" +#include "bt_utils.h" +#include "bta/include/bta_api.h" +#include "btif/include/btif_a2dp_source.h" +#include "btif_common.h" +#include +#include "audio_a2dp_hw/include/audio_a2dp_hw.h" +#include "btif_av_co.h" +#include "btif_util.h" +#include "btu.h" +#include "common/state_machine.h" +#include "osi/include/allocator.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "btif/include/btif_bap_config.h" +#include "bta_bap_uclient_api.h" +#include "btif_bap_codec_utils.h" +#include "bta/include/bta_csip_api.h" +#include +#include "osi/include/thread.h" +#include +#include "bta_api.h" +#include +#include +#include "btif/include/btif_vmcp.h" +#include "btif/include/btif_acm_source.h" +#include "l2c_api.h" +#include "bt_types.h" +#include "btm_int.h" +#include + +/***************************************************************************** + * Constants & Macros + *****************************************************************************/ +#define LE_AUDIO_MASK 0x00000300 +#define LE_AUDIO_NOT_AVAILABLE 0x00000100 +#define LE_AUDIO_AVAILABLE_NOT_LICENSED 0x00000200 //LC3 +#define LE_AUDIO_AVAILABLE_LICENSED 0x00000300 //LC3Q +#define LE_AUDIO_CS_3_1ST_BYTE_INDEX 0x00 +#define LE_AUDIO_CS_3_2ND_BYTE_INDEX 0x01 +#define LE_AUDIO_CS_3_3RD_BYTE_INDEX 0x02 +#define LE_AUDIO_CS_3_4TH_BYTE_INDEX 0x03 +#define LE_AUDIO_CS_3_5TH_BYTE_INDEX 0x04 +#define LE_AUDIO_CS_3_7TH_BYTE_INDEX 0x06 +#define LE_AUDIO_CS_3_8TH_BYTE_INDEX 0x07 + +static RawAddress active_bda = {}; +static constexpr int kDefaultMaxConnectedAudioDevices = 5; +CodecConfig current_active_config; +static CodecConfig current_media_config; +static CodecConfig current_voice_config; +static CodecConfig current_recording_config; +uint16_t current_active_profile_type = 0; +uint16_t current_active_context_type; + +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClient; +using bluetooth::bap::ucast::StreamState; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecPriority; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecBPS; +using bluetooth::bap::pacs::CodecChannelMode; +using bluetooth::bap::pacs::CodecFrameDuration; +using bluetooth::bap::ucast::CodecQosConfig; +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; +using bluetooth::bap::ucast::StreamReconfig; +using bluetooth::bap::ucast::CISConfig; +using bluetooth::bap::pacs::CodecDirection; +using bluetooth::bap::ucast::CONTENT_TYPE_MEDIA; +using bluetooth::bap::ucast::CONTENT_TYPE_CONVERSATIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_LIVE; +using bluetooth::bap::ucast::CONTENT_TYPE_UNSPECIFIED; +using bluetooth::bap::ucast::CONTENT_TYPE_INSTRUCTIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_NOTIFICATIONS; +using bluetooth::bap::ucast::CONTENT_TYPE_ALERT; +using bluetooth::bap::ucast::CONTENT_TYPE_MAN_MACHINE; +using bluetooth::bap::ucast::CONTENT_TYPE_EMERGENCY; +using bluetooth::bap::ucast::CONTENT_TYPE_RINGTONE; +using bluetooth::bap::ucast::CONTENT_TYPE_SOUND_EFFECTS; +using bluetooth::bap::ucast::CONTENT_TYPE_GAME; + +using bluetooth::bap::ucast::ASE_DIRECTION_SRC; +using bluetooth::bap::ucast::ASE_DIRECTION_SINK; +using bluetooth::bap::ucast::ASCSConfig; +using bluetooth::bap::ucast::LE_2M_PHY; +using bluetooth::bap::ucast::LE_QHS_PHY; + +using base::Bind; +using base::Unretained; +using base::IgnoreResult; +using bluetooth::Uuid; +extern void do_in_bta_thread(const base::Location& from_here, + const base::Closure& task); + +bool reconfig_acm_initiator(const RawAddress& peer_address, int profileType); + +static void btif_acm_initiator_dispatch_sm_event(const RawAddress& peer_address, + btif_acm_sm_event_t event); +void btif_acm_update_lc3q_params(int64_t* cs3, tBTIF_ACM* p_acm_data); + +uint16_t btif_acm_bap_to_acm_context(uint16_t bap_context); + +std::mutex acm_session_wait_mutex_; +std::condition_variable acm_session_wait_cv; +bool acm_session_wait; + + +/***************************************************************************** + * Local type definitions + *****************************************************************************/ + +class BtifCsipEvent { + public: + BtifCsipEvent(uint32_t event, const void* p_data, size_t data_length); + BtifCsipEvent(const BtifCsipEvent& other); + BtifCsipEvent() = delete; + ~BtifCsipEvent(); + BtifCsipEvent& operator=(const BtifCsipEvent& other); + + uint32_t Event() const { return event_; } + void* Data() const { return data_; } + size_t DataLength() const { return data_length_; } + std::string ToString() const; + static std::string EventName(uint32_t event); + + private: + void DeepCopy(uint32_t event, const void* p_data, size_t data_length); + void DeepFree(); + + uint32_t event_; + void* data_; + size_t data_length_; +}; + +class BtifAcmEvent { + public: + BtifAcmEvent(uint32_t event, const void* p_data, size_t data_length); + BtifAcmEvent(const BtifAcmEvent& other); + BtifAcmEvent() = delete; + ~BtifAcmEvent(); + BtifAcmEvent& operator=(const BtifAcmEvent& other); + + uint32_t Event() const { return event_; } + void* Data() const { return data_; } + size_t DataLength() const { return data_length_; } + std::string ToString() const; + static std::string EventName(uint32_t event); + + private: + void DeepCopy(uint32_t event, const void* p_data, size_t data_length); + void DeepFree(); + + uint32_t event_; + void* data_; + size_t data_length_; +}; + +class BtifAcmPeer; + +class BtifAcmStateMachine : public bluetooth::common::StateMachine { + public: + enum { + kStateIdle, // ACM state disconnected + kStateOpening, // ACM state connecting + kStateOpened, // ACM state connected + kStateStarted, // ACM state streaming + kStateReconfiguring, // ACM state reconfiguring + kStateClosing, // ACM state disconnecting + }; + + class StateIdle : public State { + public: + StateIdle(BtifAcmStateMachine& sm) + : State(sm, kStateIdle), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateOpening : public State { + public: + StateOpening(BtifAcmStateMachine& sm) + : State(sm, kStateOpening), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateOpened : public State { + public: + StateOpened(BtifAcmStateMachine& sm) + : State(sm, kStateOpened), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateStarted : public State { + public: + StateStarted(BtifAcmStateMachine& sm) + : State(sm, kStateStarted), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateReconfiguring : public State { + public: + StateReconfiguring(BtifAcmStateMachine& sm) + : State(sm, kStateReconfiguring), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateClosing : public State { + public: + StateClosing(BtifAcmStateMachine& sm) + : State(sm, kStateClosing), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + BtifAcmStateMachine(BtifAcmPeer& btif_acm_peer) : peer_(btif_acm_peer) { + state_idle_ = new StateIdle(*this); + state_opening_ = new StateOpening(*this); + state_opened_ = new StateOpened(*this); + state_started_ = new StateStarted(*this); + state_reconfiguring_ = new StateReconfiguring(*this); + state_closing_ = new StateClosing(*this); + + AddState(state_idle_); + AddState(state_opening_); + AddState(state_opened_); + AddState(state_started_); + AddState(state_reconfiguring_); + AddState(state_closing_); + SetInitialState(state_idle_); + } + + BtifAcmPeer& Peer() { return peer_; } + + private: + BtifAcmPeer& peer_; + StateIdle* state_idle_; + StateOpening* state_opening_; + StateOpened* state_opened_; + StateStarted* state_started_; + StateReconfiguring* state_reconfiguring_; + StateClosing* state_closing_; +}; + +class BtifAcmPeer { + public: + enum { + kFlagPendingLocalSuspend = 0x01, + kFlagPendingReconfigure = 0x02, + kFlagPendingStart = 0x04, + kFlagPendingStop = 0x08, + kFLagPendingStartAfterReconfig = 0x10, + }; + + enum { + kFlagAggresiveMode = 0x01, + kFlagRelaxedMode = 0x02, + }; + + static constexpr uint64_t kTimeoutLockReleaseMs = 5 * 1000; + + BtifAcmPeer(const RawAddress& peer_address, uint8_t peer_sep, + uint8_t set_id, uint8_t cig_id, uint8_t cis_id); + ~BtifAcmPeer(); + + bt_status_t Init(); + void Cleanup(); + + /** + * Check whether the peer can be deleted. + * + * @return true if the pair can be deleted, otherwise false + */ + bool CanBeDeleted() const; + + bool IsPeerActiveForMusic() const { + return (SetId() == MusicActiveSetId()); + } + bool IsPeerActiveForVoice() const { + return (SetId() == VoiceActiveSetId()); + } + + bool IsAcceptor() const { return (peer_sep_ == ACM_TSEP_SNK); } + + const RawAddress& MusicActivePeerAddress() const; + const RawAddress& VoiceActivePeerAddress() const; + uint8_t MusicActiveSetId() const; + uint8_t VoiceActiveSetId() const; + + const RawAddress& PeerAddress() const { return peer_address_; } + + void SetContextType(uint16_t contextType) { context_type_ = context_type_ | contextType; } + uint16_t GetContextType() { return context_type_; } + void ResetContextType(uint16_t contextType) { context_type_ &= ~contextType; } + + + void SetProfileType(uint16_t profileType) { profile_type_ = profile_type_ | profileType; } + uint16_t GetProfileType() {return profile_type_;} + void ResetProfileType(uint16_t profileType) { profile_type_ &= ~profileType; } + + + void SetRcfgProfileType(uint16_t profileType) { rcfg_profile_type_ = profileType; } + uint16_t GetRcfgProfileType() {return rcfg_profile_type_;} + + void SetPrefContextType(uint16_t preferredContext) {preferred_context_ = preferredContext;}; + uint16_t GetPrefContextType() {return preferred_context_;} + + void SetStreamContextType(uint16_t contextType) { stream_context_type_ = contextType; } + uint16_t GetStreamContextType() { return stream_context_type_; } + + void SetPeerVoiceRxState(StreamState state) {voice_rx_state = state;} + StreamState GetPeerVoiceRxState() {return voice_rx_state;} + + void SetPeerVoiceTxState(StreamState state) {voice_tx_state = state;} + StreamState GetPeerVoiceTxState() {return voice_tx_state;} + + void SetPeerMusicTxState(StreamState state) {music_tx_state = state;} + StreamState GetPeerMusicTxState() {return music_tx_state;} + + void SetPeerMusicRxState(StreamState state) {music_rx_state = state;} + StreamState GetPeerMusicRxState() {return music_rx_state;} + + void SetPeerLatency(uint16_t peerLatency) { peer_latency_ = peerLatency; } + uint16_t GetPeerLatency() {return peer_latency_;} + + void SetIsStereoHsType(bool stereoHsType) { is_stereohs_type_= stereoHsType; } + bool IsStereoHsType() {return is_stereohs_type_;} + + void set_peer_media_codec_config(CodecConfig &codec_config) { + peer_media_codec_config = codec_config; + } + CodecConfig get_peer_media_codec_config() {return peer_media_codec_config;} + + void set_peer_media_qos_config(QosConfig &qos_config) {peer_media_qos_config = qos_config;} + QosConfig get_peer_media_qos_config() {return peer_media_qos_config;} + + void set_peer_media_codec_qos_config(CodecQosConfig &codec_qos_config) { + peer_media_codec_qos_config = codec_qos_config;} + CodecQosConfig get_peer_media_codec_qos_config() {return peer_media_codec_qos_config;} + + void set_peer_voice_rx_codec_config(CodecConfig &codec_config) { + peer_voice_rx_codec_config = codec_config; + } + CodecConfig get_peer_voice_rx_codec_config() {return peer_voice_rx_codec_config;} + + void set_peer_voice_rx_qos_config(QosConfig &qos_config) {peer_voice_rx_qos_config = qos_config;} + QosConfig get_peer_voice_rx_qos_config() {return peer_voice_rx_qos_config;} + + void set_peer_voice_rx_codec_qos_config(CodecQosConfig &codec_qos_config) { + peer_voice_rx_codec_qos_config = codec_qos_config;} + CodecQosConfig get_peer_voice_rx_codec_qos_config() {return peer_voice_rx_codec_qos_config;} + + void set_peer_voice_tx_codec_config(CodecConfig &codec_config) { + peer_voice_tx_codec_config = codec_config; + } + CodecConfig get_peer_voice_tx_codec_config() {return peer_voice_tx_codec_config;} + + void set_peer_voice_tx_qos_config(QosConfig &qos_config) {peer_voice_tx_qos_config = qos_config;} + QosConfig get_peer_voice_tx_qos_config() {return peer_voice_tx_qos_config;} + + void set_peer_voice_tx_codec_qos_config(CodecQosConfig &codec_qos_config) { + peer_voice_tx_codec_qos_config = codec_qos_config;} + CodecQosConfig get_peer_voice_tx_codec_qos_config() {return peer_voice_tx_codec_qos_config;} + + uint8_t SetId() const { return set_id_; } + uint8_t CigId() const { return cig_id_; } + uint8_t CisId() const { return cis_id_; } + + BtifAcmStateMachine& StateMachine() { return state_machine_; } + const BtifAcmStateMachine& StateMachine() const { return state_machine_; } + + bool IsConnected() const; + bool IsStreaming() const; + + bool CheckConnUpdateMode(uint8_t mode) const { + return (conn_mode_ == mode); + } + + void SetConnUpdateMode(uint8_t mode) { + if(conn_mode_ == mode) return; + if(mode == kFlagAggresiveMode) { + BTIF_TRACE_DEBUG("%s: push aggressive intervals", __func__); + L2CA_UpdateBleConnParams(peer_address_, 16, 32, 0, 1000); + } else if(mode == kFlagRelaxedMode) { + BTIF_TRACE_DEBUG("%s: push relaxed intervals", __func__); + L2CA_UpdateBleConnParams(peer_address_, 40, 56, 0, 1000); + } + conn_mode_ = mode; + } + + void ClearConnUpdateMode() { conn_mode_ = 0; } + + bool CheckFlags(uint8_t flags_mask) const { + return ((flags_ & flags_mask) != 0); + } + + /** + * Set only the flags as specified by the flags mask. + * + * @param flags_mask the flags to set + */ + void SetFlags(uint8_t flags_mask) { flags_ |= flags_mask; } + + /** + * Clear only the flags as specified by the flags mask. + * + * @param flags_mask the flags to clear + */ + void ClearFlags(uint8_t flags_mask) { flags_ &= ~flags_mask; } + + /** + * Clear all the flags. + */ + void ClearAllFlags() { flags_ = 0; } + + /** + * Get string for the flags set. + */ + std::string FlagsToString() const; + + private: + const RawAddress peer_address_; + const uint8_t peer_sep_;// SEP type of peer device + uint8_t set_id_, cig_id_, cis_id_; + BtifAcmStateMachine state_machine_; + uint8_t flags_; + uint8_t conn_mode_; + bool is_stereohs_type_ = false; + StreamState voice_rx_state, voice_tx_state, music_tx_state, music_rx_state; + uint16_t peer_latency_; + uint16_t context_type_ = 0; + uint16_t profile_type_ = 0; + uint16_t rcfg_profile_type_ = 0; + uint16_t preferred_context_ = 0; + uint16_t stream_context_type_ = 0; + CodecConfig peer_media_codec_config, peer_voice_rx_codec_config, peer_voice_tx_codec_config; + QosConfig peer_media_qos_config, peer_voice_rx_qos_config, peer_voice_tx_qos_config; + CodecQosConfig peer_media_codec_qos_config, peer_voice_rx_codec_qos_config, peer_voice_tx_codec_qos_config; +}; + +static void btif_acm_check_and_cancel_lock_release_timer(uint8_t setId); +bool btif_acm_request_csip_unlock(uint8_t setId); +void btif_acm_process_request(tA2DP_CTRL_CMD cmd); + +void btif_acm_source_on_stopped(); +void btif_acm_source_on_suspended(); +void btif_acm_on_idle(void); +bool btif_acm_check_if_requested_devices_stopped(); + +void btif_acm_source_cleanup(void); + +bt_status_t btif_acm_source_setup_codec(); +uint16_t btif_acm_get_active_device_latency(); + +class BtifAcmInitiator { + public: + static constexpr uint8_t kCigIdMin = 0; + static constexpr uint8_t kCigIdMax = BTA_ACM_NUM_CIGS; + static constexpr uint8_t kPeerMinSetId = BTA_ACM_MIN_NUM_SETID; + static constexpr uint8_t kPeerMaxSetId = BTA_ACM_MAX_NUM_SETID; + + enum { + kFlagStatusUnknown = 0x0, + kFlagStatusUnlocked = 0x1, + kFlagStatusPendingLock = 0x2, + kFlagStatusSubsetLocked = 0x4, + kFlagStatusLocked = 0x8, + kFlagStatusPendingUnlock = 0x10, + }; + + // acm group procedure timer + static constexpr uint64_t kTimeoutAcmGroupProcedureMs = 10 * 1000; + static constexpr uint64_t kTimeoutConnIntervalMs = 5 * 1000; + + BtifAcmInitiator() + : callbacks_(nullptr), + enabled_(false), + max_connected_peers_(kDefaultMaxConnectedAudioDevices), + music_active_setid_(INVALID_SET_ID), + voice_active_setid_(INVALID_SET_ID), + csip_app_id_(0), + is_csip_reg_(false), + lock_flags_(0), + music_set_lock_release_timer_(nullptr), + voice_set_lock_release_timer_(nullptr), + acm_group_procedure_timer_(nullptr), + acm_conn_interval_timer_(nullptr){} + ~BtifAcmInitiator(); + + bt_status_t Init( + btacm_initiator_callbacks_t* callbacks, int max_connected_audio_devices, + const std::vector& codec_priorities); + void Cleanup(); + bool IsSetIdle(uint8_t setId) const; + + btacm_initiator_callbacks_t* Callbacks() { return callbacks_; } + bool Enabled() const { return enabled_; } + + BtifAcmPeer* FindPeer(const RawAddress& peer_address); + uint8_t FindPeerSetId(const RawAddress& peer_address); + uint8_t FindPeerBySetId(uint8_t set_id); + uint8_t FindPeerCigId(uint8_t set_id); + uint8_t FindPeerByCigId(uint8_t cig_id); + uint8_t FindPeerByCisId(uint8_t cig_id, uint8_t cis_id); + BtifAcmPeer* FindOrCreatePeer(const RawAddress& peer_address); + BtifAcmPeer* FindMusicActivePeer(); + + /** + * Check whether a connection to a peer is allowed. + * The check considers the maximum number of connected peers. + * + * @param peer_address the peer address to connect to + * @return true if connection is allowed, otherwise false + */ + bool AllowedToConnect(const RawAddress& peer_address) const; + bool IsAcmIdle() const; + + bool IsOtherSetPeersIdle(const RawAddress& peer_address, uint8_t setId) const; + + alarm_t* MusicSetLockReleaseTimer() { return music_set_lock_release_timer_; } + alarm_t* VoiceSetLockReleaseTimer() { return voice_set_lock_release_timer_; } + alarm_t* AcmGroupProcedureTimer() { return acm_group_procedure_timer_; } + alarm_t* AcmConnIntervalTimer() { return acm_conn_interval_timer_; } + + /** + * Delete a peer. + * + * @param peer_address of the peer to be deleted + * @return true on success, false on failure + */ + bool DeletePeer(const RawAddress& peer_address); + + /** + * Delete all peers that are in Idle state and can be deleted. + */ + void DeleteIdlePeers(); + + /** + * Get the Music active peer. + * + * @return the music active peer + */ + const RawAddress& MusicActivePeer() const { return music_active_peer_; } + + /** + * Get the Voice active peer. + * + * @return the voice active peer + */ + const RawAddress& VoiceActivePeer() const { return voice_active_peer_; } + + uint8_t MusicActiveCSetId() const { return music_active_setid_; } + uint8_t VoiceActiveCSetId() const { return voice_active_setid_; } + + void SetCsipAppId(uint8_t csip_app_id) { csip_app_id_ = csip_app_id; } + uint8_t GetCsipAppId() const { return csip_app_id_; } + + void SetCsipRegistration(bool is_csip_reg) { is_csip_reg_ = is_csip_reg; } + bool IsCsipRegistered() const { return is_csip_reg_;} + + void SetMusicActiveGroupStarted(bool flag) { is_music_active_set_started_ = flag; } + bool IsMusicActiveGroupStarted () { return is_music_active_set_started_; } + + bool IsConnUpdateEnabled() const { + return (is_conn_update_enabled_ == true); + } + + void SetOrUpdateGroupLockStatus(uint8_t set_id, int lock_status) { + std::map::iterator p = set_lock_status_.find(set_id); + if (p == set_lock_status_.end()) { + set_lock_status_.insert(std::make_pair(set_id, lock_status)); + } else { + set_lock_status_.erase(set_id); + set_lock_status_.insert(std::make_pair(set_id, lock_status)); + } + } + + int GetGroupLockStatus(uint8_t set_id) { + auto it = set_lock_status_.find(set_id); + if (it != set_lock_status_.end()) return it->second; + return kFlagStatusUnknown; + } + + bool CheckLockFlags(uint8_t bitlockflags_mask) const { + return ((lock_flags_ & bitlockflags_mask) != 0); + } + + /** + * Set only the flags as specified by the bitlockflags_mask. + * + * @param bitlockflags_mask the lock flags to set + */ + void SetLockFlags(uint8_t bitlockflags_mask) { lock_flags_ |= bitlockflags_mask;} + + /** + * Clear only the flags as specified by the bitlockflags_mask. + * + * @param bitlockflags_mask the lock flags to clear + */ + void ClearLockFlags(uint8_t bitlockflags_mask) { lock_flags_ &= ~bitlockflags_mask;} + + /** + * Clear all lock flags. + */ + void ClearAllLockFlags() { lock_flags_ = 0;} + + /** + * Get a string for lock flags. + */ + std::string LockFlagsToString() const; + + bool SetAcmActivePeer(const RawAddress& peer_address, uint16_t contextType, uint16_t profileType, + std::promise peer_ready_promise) { + LOG(INFO) << __PRETTY_FUNCTION__ << ": peer: " << peer_address + << " music_active_peer_: " << music_active_peer_ << " voice_active_peer_: " << voice_active_peer_; + uint16_t sink_latency; + active_bda = peer_address;// for stereo LEA active_bda = peer_address + BtifAcmPeer* peer = FindPeer(peer_address); + BTIF_TRACE_DEBUG("%s address byte BDA:%02x", __func__,active_bda.address[5]); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (music_active_peer_ == active_bda) { + //Same active device, profileType may have changed. + if ((peer != nullptr) && (current_active_profile_type != 0) && (current_active_profile_type != profileType)) { + BTIF_TRACE_DEBUG("%s current_active_profile_type %d, profileType %d peer->GetProfileType() %d", + __func__, current_active_profile_type, profileType, peer->GetProfileType()); + if ((peer->GetProfileType() & profileType) == 0) { + std::unique_lock guard(acm_session_wait_mutex_); + acm_session_wait = false; + if (reconfig_acm_initiator(peer_address, profileType)) { + acm_session_wait_cv.wait_for(guard, std::chrono::milliseconds(3000), []{return acm_session_wait;}); + BTIF_TRACE_EVENT("%s: done with signal",__func__); + } + } else { + current_active_profile_type = profileType; + if (current_active_profile_type != WMCP) + current_active_config = current_media_config; + else + current_active_config = current_recording_config; + if (!btif_acm_source_restart_session(music_active_peer_, active_bda)) { + // cannot set promise but need to be handled within restart_session + return false; + } + if (current_active_profile_type == WMCP) { + sink_latency = btif_acm_get_active_device_latency(); + BTIF_TRACE_EVENT("%s: sink_latency = %dms", __func__, sink_latency); + if ((sink_latency > 0) && !btif_acm_update_sink_latency_change(sink_latency * 10)) { + BTIF_TRACE_ERROR("%s: unable to update latency", __func__); + } + } + } + peer_ready_promise.set_value(); + return true; + } else { + peer_ready_promise.set_value(); + return true; + } + } + + if (active_bda.IsEmpty()) { + BTIF_TRACE_EVENT("%s: set address is empty, shutdown the Acm initiator", + __func__); + btif_acm_check_and_cancel_lock_release_timer(music_active_setid_); + if ((GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusLocked) || + (GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusSubsetLocked)) { + if (!btif_acm_request_csip_unlock(music_active_setid_)) { + BTIF_TRACE_ERROR("%s: error unlocking", __func__); + } + } + btif_acm_source_end_session(music_active_peer_); + music_active_peer_ = active_bda; + current_active_profile_type = 0; + memset(¤t_active_config, 0, sizeof(current_active_config)); + peer_ready_promise.set_value(); + return true; + } + + btif_acm_check_and_cancel_lock_release_timer(music_active_setid_); + if ((GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusLocked) || + (GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusSubsetLocked)) { + if (!btif_acm_request_csip_unlock(music_active_setid_)) { + BTIF_TRACE_ERROR("%s: error unlocking", __func__); + } + } + + /*check if previous active device is streaming, then STOP it first*/ + if (!music_active_peer_.IsEmpty()) { + int setid = music_active_setid_; + if (setid < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setid); + if (cset_info.size != 0) { + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* grp_peer = FindPeer(*itr); + if (grp_peer != nullptr && grp_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: peer is streaming %s ", __func__, grp_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + } + } else { + BTIF_TRACE_DEBUG("%s: music_active_peer_ is twm device ", __func__); + BtifAcmPeer* twm_peer = FindPeer(music_active_peer_); + if (twm_peer != nullptr && twm_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: music_active_peer_ %s is streaming, send stop ", __func__, twm_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(music_active_peer_, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + + if ((peer != nullptr) && ((peer->GetProfileType() & profileType) == 0)) { + BTIF_TRACE_DEBUG("%s peer.GetProfileType() %d, profileType %d", __func__, peer->GetProfileType(), profileType); + std::unique_lock guard(acm_session_wait_mutex_); + acm_session_wait = false; + if (reconfig_acm_initiator(peer_address, profileType)) { + acm_session_wait_cv.wait_for(guard, std::chrono::milliseconds(3000), []{return acm_session_wait;}); + BTIF_TRACE_EVENT("%s: done with signal",__func__); + } + } else { + current_active_profile_type = profileType; + if (current_active_profile_type != WMCP) + current_active_config = current_media_config; + else + current_active_config = current_recording_config; + if (!btif_acm_source_restart_session(music_active_peer_, active_bda)) { + // cannot set promise but need to be handled within restart_session + return false; + } + } + music_active_peer_ = active_bda; + if (active_bda.address[0] == 0x9E && active_bda.address[1] == 0x8B && active_bda.address[2] == 0x00) { + BTIF_TRACE_DEBUG("%s: get set ID from group BD address ", __func__); + music_active_setid_ = active_bda.address[5]; + } else { + BTIF_TRACE_DEBUG("%s: get set ID from peer data ", __func__); + if (peer != nullptr) + music_active_setid_ = peer->SetId(); + } + + if (current_active_profile_type == WMCP) { + sink_latency = btif_acm_get_active_device_latency(); + BTIF_TRACE_EVENT("%s: sink_latency = %dms", __func__, sink_latency); + if ((sink_latency > 0) && !btif_acm_update_sink_latency_change(sink_latency * 10)) { + BTIF_TRACE_ERROR("%s: unable to update latency", __func__); + } + } + peer_ready_promise.set_value(); + return true; + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (voice_active_peer_ == active_bda) { + peer_ready_promise.set_value(); + return true; + } + if (active_bda.IsEmpty()) { + BTIF_TRACE_EVENT("%s: peer address is empty, shutdown the acm initiator", + __func__); + voice_active_peer_ = active_bda; + peer_ready_promise.set_value(); + return true; + } + + /*check if previous active device is streaming, then STOP it first*/ + if (!voice_active_peer_.IsEmpty()) { + int setid = voice_active_setid_; + if (setid < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setid); + if (cset_info.size != 0) { + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* grp_peer = FindPeer(*itr); + if (grp_peer != nullptr && grp_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: voice peer is streaming %s ", __func__, grp_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + } + } else { + BTIF_TRACE_DEBUG("%s: voice_active_peer_ is twm device ", __func__); + BtifAcmPeer* twm_peer = FindPeer(voice_active_peer_); + if (twm_peer != nullptr && twm_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: voice_active_peer_ %s is streaming, send stop ", __func__, twm_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(voice_active_peer_, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + + voice_active_peer_ = active_bda; + if (active_bda.address[0] == 0x9E && active_bda.address[1] == 0x8B && active_bda.address[2] == 0x00) { + BTIF_TRACE_DEBUG("%s: get set ID from group BD address ", __func__); + voice_active_setid_ = active_bda.address[5]; + } else { + BTIF_TRACE_DEBUG("%s: get set ID from peer data ", __func__); + if (peer != nullptr) + voice_active_setid_ = peer->SetId(); + } + peer_ready_promise.set_value(); + return true; + } else { + peer_ready_promise.set_value(); + return true; + } + } + + void btif_acm_initiator_encoder_user_config_update_req( + const RawAddress& peer_addr, + const std::vector& codec_user_preferences, + std::promise peer_ready_promise); + + + void UpdateCodecConfig( + const RawAddress& peer_address, + const std::vector& codec_preferences, + int contextType, + int profileType, + std::promise peer_ready_promise) { + // Restart the session if the codec for the active peer is updated + if (!peer_address.IsEmpty() && music_active_peer_ == peer_address) { + btif_acm_source_end_session(music_active_peer_); + } + + btif_acm_initiator_encoder_user_config_update_req( + peer_address, codec_preferences, std::move(peer_ready_promise)); + } + + const std::map& Peers() const { return peers_; } + // const std::map& SetPeers() const { return set_peers_; } + + std::vector locked_devices; + private: + void CleanupAllPeers(); + + btacm_initiator_callbacks_t* callbacks_; + bool enabled_; + int max_connected_peers_; + + RawAddress music_active_peer_; + RawAddress voice_active_peer_; + uint8_t music_active_setid_; + uint8_t voice_active_setid_; + uint8_t music_active_set_locked_dev_count_; + uint8_t voice_active_set_locked_dev_count_; + bool is_music_active_set_started_; + bool is_voice_active_set_started_; + bool is_conn_update_enabled_; + + uint8_t csip_app_id_; + bool is_csip_reg_; + uint8_t lock_flags_; + + alarm_t* music_set_lock_release_timer_; + alarm_t* voice_set_lock_release_timer_; + alarm_t* acm_group_procedure_timer_; + alarm_t* acm_conn_interval_timer_; + + + std::map peers_; + std::map addr_setid_pair; + std::map set_cig_pair;//setid and cig id pair + std::map > cig_cis_pair;//cig id and cis id pair + std::map set_lock_status_; +}; + + +/***************************************************************************** + * Static variables + *****************************************************************************/ +static BtifAcmInitiator btif_acm_initiator; +std::vector unicast_codecs_capabilities; +static CodecConfig acm_local_capability = + {CodecIndex::CODEC_INDEX_SOURCE_LC3, + CodecPriority::CODEC_PRIORITY_DEFAULT, + CodecSampleRate::CODEC_SAMPLE_RATE_48000, + CodecBPS::CODEC_BITS_PER_SAMPLE_24, + CodecChannelMode::CODEC_CHANNEL_MODE_STEREO, 0, 0, 0, 0}; +static CodecConfig default_config; +static bool mandatory_codec_selected = false; +static bt_status_t disconnect_acm_initiator(const RawAddress& peer_address, + uint16_t contextType); + +static bt_status_t start_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType); +static bt_status_t stop_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType); + +static void btif_acm_handle_csip_status_locked(std::vector addr, uint8_t setId); + +static void btif_acm_handle_evt(uint16_t event, char* p_param); +static void btif_report_connection_state(const RawAddress& peer_address, + btacm_connection_state_t state, uint16_t contextType); +static void btif_report_audio_state(const RawAddress& peer_address, + btacm_audio_state_t state, uint16_t contextType); + +static void btif_acm_check_and_start_lock_release_timer(uint8_t setId); + +static void btif_acm_initiator_lock_release_timer_timeout(void* data); + +static void btif_acm_check_and_start_group_procedure_timer(uint8_t setId); +static void btif_acm_check_and_start_conn_Interval_timer(BtifAcmPeer* peer); +static void btif_acm_initiator_conn_Interval_timer_timeout(void *data); +static void btif_acm_check_and_cancel_conn_Interval_timer(); + + +static void btif_acm_check_and_cancel_group_procedure_timer(uint8_t setId); +static void btif_acm_initiator_group_procedure_timer_timeout(void *data); +static void SelectCodecQosConfig(const RawAddress& bd_addr, int profile_type, + int context_type, int direction, int config_type); +bool compare_codec_config_(CodecConfig &first, CodecConfig &second); +void print_codec_parameters(CodecConfig config); +void print_qos_parameters(QosConfig qos_config); +void select_best_codec_config(const RawAddress& bd_addr, uint16_t context_type, + uint8_t profile_type, CodecConfig *codec_config, int dir, int config_type); +static UcastClientInterface* sUcastClientInterface = nullptr; + +/***************************************************************************** + * Local helper functions + *****************************************************************************/ + +const char* dump_acm_sm_event_name(btif_acm_sm_event_t event) { + switch ((int)event) { + CASE_RETURN_STR(BTA_ACM_DISCONNECT_EVT) + CASE_RETURN_STR(BTA_ACM_CONNECT_EVT) + CASE_RETURN_STR(BTA_ACM_START_EVT) + CASE_RETURN_STR(BTA_ACM_STOP_EVT) + CASE_RETURN_STR(BTA_ACM_RECONFIG_EVT) + CASE_RETURN_STR(BTA_ACM_CONFIG_EVT) + CASE_RETURN_STR(BTIF_ACM_CONNECT_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_DISCONNECT_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_START_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_STOP_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_SUSPEND_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_RECONFIG_REQ_EVT) + CASE_RETURN_STR(BTA_ACM_CONN_UPDATE_TIMEOUT_EVT) + default: + return "UNKNOWN_EVENT"; + } +} + +const char* dump_csip_event_name(btif_csip_sm_event_t event) { + switch ((int)event) { + CASE_RETURN_STR(BTA_CSIP_NEW_SET_FOUND_EVT) + CASE_RETURN_STR(BTA_CSIP_SET_MEMBER_FOUND_EVT) + CASE_RETURN_STR(BTA_CSIP_CONN_STATE_CHG_EVT) + CASE_RETURN_STR(BTA_CSIP_LOCK_STATUS_CHANGED_EVT) + CASE_RETURN_STR(BTA_CSIP_LOCK_AVAILABLE_EVT) + CASE_RETURN_STR(BTA_CSIP_SET_SIZE_CHANGED) + CASE_RETURN_STR(BTA_CSIP_SET_SIRK_CHANGED) + default: + return "UNKNOWN_EVENT"; + } +} + +void btif_acm_signal_session_ready() { + std::unique_lock guard(acm_session_wait_mutex_); + if(!acm_session_wait) { + acm_session_wait = true; + acm_session_wait_cv.notify_all(); + } else { + BTIF_TRACE_WARNING("%s: already signalled ",__func__); + } +} + +void fetch_media_tx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_media) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), profile_type, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } else { + //EB config + SelectCodecQosConfig(peer->PeerAddress(), profile_type, MEDIA_CONTEXT, SNK, EB_CONFIG); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } + conn_media->stream_type.type = CONTENT_TYPE_MEDIA; + conn_media->stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_media->stream_type.direction = ASE_DIRECTION_SINK; +} + +void fetch_media_rx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_media) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), WMCP, MEDIA_CONTEXT, SRC, STEREO_HS_CONFIG_1); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } else { + //EB config + SelectCodecQosConfig(peer->PeerAddress(), WMCP, MEDIA_CONTEXT, SRC, EB_CONFIG); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } + conn_media->stream_type.type = CONTENT_TYPE_MEDIA; + conn_media->stream_type.audio_context = CONTENT_TYPE_LIVE; //Live audio context + conn_media->stream_type.direction = ASE_DIRECTION_SRC; +} + +void fetch_voice_rx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_voice) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SRC, STEREO_HS_CONFIG_1); + conf = peer->get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } else { + // EB config + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SRC, EB_CONFIG); + conf = peer->get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } + conn_voice->stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.direction = ASE_DIRECTION_SRC; +} + +void fetch_voice_tx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_voice) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SNK, STEREO_HS_CONFIG_1); + conf = peer->get_peer_voice_tx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } else { + // EB config + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SNK, EB_CONFIG); + conf = peer->get_peer_voice_tx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } + conn_voice->stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.direction = ASE_DIRECTION_SINK; +} + +BtifAcmEvent::BtifAcmEvent(uint32_t event, const void* p_data, size_t data_length) + : event_(event), data_(nullptr), data_length_(0) { + DeepCopy(event, p_data, data_length); +} + +BtifAcmEvent::BtifAcmEvent(const BtifAcmEvent& other) + : event_(0), data_(nullptr), data_length_(0) { + *this = other; +} + +BtifAcmEvent& BtifAcmEvent::operator=(const BtifAcmEvent& other) { + DeepFree(); + DeepCopy(other.Event(), other.Data(), other.DataLength()); + return *this; +} + +BtifAcmEvent::~BtifAcmEvent() { DeepFree(); } + +std::string BtifAcmEvent::ToString() const { + return BtifAcmEvent::EventName(event_); +} + +std::string BtifAcmEvent::EventName(uint32_t event) { + std::string name = dump_acm_sm_event_name((btif_acm_sm_event_t)event); + std::stringstream ss_value; + ss_value << "(0x" << std::hex << event << ")"; + return name + ss_value.str(); +} + +void BtifAcmEvent::DeepCopy(uint32_t event, const void* p_data, + size_t data_length) { + event_ = event; + data_length_ = data_length; + if (data_length == 0) { + data_ = nullptr; + } else { + data_ = osi_malloc(data_length_); + memcpy(data_, p_data, data_length); + } +} + +void BtifAcmEvent::DeepFree() { + osi_free_and_reset((void**)&data_); + data_length_ = 0; +} + +BtifCsipEvent::BtifCsipEvent(uint32_t event, const void* p_data, size_t data_length) + : event_(event), data_(nullptr), data_length_(0) { + DeepCopy(event, p_data, data_length); +} + +BtifCsipEvent::BtifCsipEvent(const BtifCsipEvent& other) + : event_(0), data_(nullptr), data_length_(0) { + *this = other; +} + +BtifCsipEvent& BtifCsipEvent::operator=(const BtifCsipEvent& other) { + DeepFree(); + DeepCopy(other.Event(), other.Data(), other.DataLength()); + return *this; +} + +BtifCsipEvent::~BtifCsipEvent() { DeepFree(); } + +std::string BtifCsipEvent::ToString() const { + return BtifCsipEvent::EventName(event_); +} + +std::string BtifCsipEvent::EventName(uint32_t event) { + std::string name = dump_csip_event_name((btif_csip_sm_event_t)event); + std::stringstream ss_value; + ss_value << "(0x" << std::hex << event << ")"; + return name + ss_value.str(); +} + +void BtifCsipEvent::DeepCopy(uint32_t event, const void* p_data, + size_t data_length) { + event_ = event; + data_length_ = data_length; + if (data_length == 0) { + data_ = nullptr; + } else { + data_ = osi_malloc(data_length_); + memcpy(data_, p_data, data_length); + } +} + +void BtifCsipEvent::DeepFree() { + osi_free_and_reset((void**)&data_); + data_length_ = 0; +} + +BtifAcmPeer::BtifAcmPeer(const RawAddress& peer_address, uint8_t peer_sep, + uint8_t set_id, uint8_t cig_id, uint8_t cis_id) + : peer_address_(peer_address), + peer_sep_(peer_sep), + set_id_(set_id), + cig_id_(cig_id), + cis_id_(cis_id), + state_machine_(*this), + flags_(0) {} + +BtifAcmPeer::~BtifAcmPeer() { /*alarm_free(av_open_on_rc_timer_);*/ } + +std::string BtifAcmPeer::FlagsToString() const { + std::string result; + + if (flags_ & BtifAcmPeer::kFlagPendingLocalSuspend) { + if (!result.empty()) result += "|"; + result += "LOCAL_SUSPEND_PENDING"; + } + if (flags_ & BtifAcmPeer::kFlagPendingReconfigure) { + if (!result.empty()) result += "|"; + result += "PENDING_RECONFIGURE"; + } + if (flags_ & BtifAcmPeer::kFlagPendingStart) { + if (!result.empty()) result += "|"; + result += "PENDING_START"; + } + if (flags_ & BtifAcmPeer::kFlagPendingStop) { + if (!result.empty()) result += "|"; + result += "PENDING_STOP"; + } + if (flags_ & BtifAcmPeer::kFLagPendingStartAfterReconfig) { + if (!result.empty()) result += "|"; + result += "PENDING_START_AFTER_RECONFIG"; + } + if (result.empty()) result = "None"; + + return base::StringPrintf("0x%x(%s)", flags_, result.c_str()); +} + +bt_status_t BtifAcmPeer::Init() { + state_machine_.Start(); + return BT_STATUS_SUCCESS; +} + +void BtifAcmPeer::Cleanup() { + state_machine_.Quit(); +} + +bool BtifAcmPeer::CanBeDeleted() const { + return ( + (state_machine_.StateId() == BtifAcmStateMachine::kStateIdle) && + (state_machine_.PreviousStateId() != BtifAcmStateMachine::kStateInvalid)); +} + +const RawAddress& BtifAcmPeer::MusicActivePeerAddress() const { + return btif_acm_initiator.MusicActivePeer(); +} +const RawAddress& BtifAcmPeer::VoiceActivePeerAddress() const { + return btif_acm_initiator.VoiceActivePeer(); +} +uint8_t BtifAcmPeer::MusicActiveSetId() const { + return btif_acm_initiator.MusicActiveCSetId(); +} +uint8_t BtifAcmPeer::VoiceActiveSetId() const { + return btif_acm_initiator.VoiceActiveCSetId(); +} + +bool BtifAcmPeer::IsConnected() const { + int state = state_machine_.StateId(); + return ((state == BtifAcmStateMachine::kStateOpened) || + (state == BtifAcmStateMachine::kStateStarted)); +} + +bool BtifAcmPeer::IsStreaming() const { + int state = state_machine_.StateId(); + return (state == BtifAcmStateMachine::kStateStarted); +} + +BtifAcmInitiator::~BtifAcmInitiator() { + CleanupAllPeers(); +} + +void init_local_capabilities() { + unicast_codecs_capabilities.push_back(acm_local_capability); +} + +void BtifAcmInitiator::Cleanup() { + LOG_INFO(LOG_TAG, "%s", __PRETTY_FUNCTION__); + if (!enabled_) return; + std::promise peer_ready_promise; + btif_disable_service(BTA_ACM_INITIATOR_SERVICE_ID); // ACM deregistration required? + CleanupAllPeers(); + alarm_free(music_set_lock_release_timer_); + music_set_lock_release_timer_ = nullptr; + alarm_free(music_set_lock_release_timer_); + music_set_lock_release_timer_ = nullptr; + alarm_free(acm_group_procedure_timer_); + acm_group_procedure_timer_ = nullptr; + alarm_free(acm_conn_interval_timer_); + acm_conn_interval_timer_ = nullptr; + callbacks_ = nullptr; + enabled_ = false; + if (sUcastClientInterface != nullptr) { + sUcastClientInterface->Cleanup(); + sUcastClientInterface = nullptr; + } +} + +BtifAcmPeer* BtifAcmInitiator::FindPeer(const RawAddress& peer_address) { + auto it = peers_.find(peer_address); + if (it != peers_.end()) return it->second; + return nullptr; +} + +uint8_t BtifAcmInitiator:: FindPeerSetId(const RawAddress& peer_address) { + auto it = addr_setid_pair.find(peer_address); + if (it != addr_setid_pair.end()) return it->second; + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerBySetId(uint8_t setid) { + for (auto it : addr_setid_pair) { + if (it.second == setid) { + return setid; + } + } + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerCigId(uint8_t setid) { + auto it = set_cig_pair.find(setid); + if (it != set_cig_pair.end()) return it->second; + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerByCigId(uint8_t cigid) { + for (auto it : set_cig_pair) { + if (it.second == cigid) { + return cigid; + } + } + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerByCisId(uint8_t cigid, uint8_t cisid) { + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == cigid) { + if (ptr->second == cisid) { + return cisid; + } + } + } + } + return 0xff; +} + +BtifAcmPeer* BtifAcmInitiator::FindOrCreatePeer(const RawAddress& peer_address) { + BTIF_TRACE_DEBUG("%s: peer_address=%s ", __PRETTY_FUNCTION__, + peer_address.ToString().c_str()); + + BtifAcmPeer* peer = FindPeer(peer_address); + if (peer != nullptr) return peer; + + uint8_t SetId, CigId, CisId; + //get the set id from CSIP. + //TODO: need UUID ? + Uuid uuid = Uuid::kEmpty; + LOG_INFO(LOG_TAG, "%s ACM UUID = %s", __func__, uuid.ToString().c_str()); + SetId = BTA_CsipGetDeviceSetId(peer_address, uuid); + BTIF_TRACE_EVENT("%s: set id from csip : %d", __func__, SetId); + if (SetId == INVALID_SET_ID) { + SetId = FindPeerSetId(peer_address); + // Find next available SET ID to use + if (SetId == 0xff) { + for (SetId = kPeerMinSetId; SetId < kPeerMaxSetId; SetId++) { + if (FindPeerBySetId(SetId) == 0xff) break; + } + } + } + if (SetId == kPeerMaxSetId) { + BTIF_TRACE_ERROR( + "%s: Cannot create peer for peer_address=%s : " + "cannot allocate unique SET ID", + __PRETTY_FUNCTION__, peer_address.ToString().c_str()); + return nullptr; + } + addr_setid_pair.insert(std::make_pair(peer_address, SetId)); + + //Find next available CIG ID to use + CigId = FindPeerCigId(SetId); + if (CigId == 0xff) { + for (CigId = kCigIdMin; CigId < kCigIdMax; ) { + if (FindPeerByCigId(CigId) == 0xff) break; + CigId += 4; + } + } + if (CigId == kCigIdMax) { + BTIF_TRACE_ERROR( + "%s: cannot allocate unique CIG ID to = %s ", + __func__, peer_address.ToString().c_str()); + return nullptr; + } + set_cig_pair.insert(std::make_pair(SetId, CigId)); + + //Find next available CIS ID to use + for (CisId = kCigIdMin; CisId < kCigIdMax; CisId++) { + if (FindPeerByCisId(CigId, CisId) == 0xff) break; + } + if (CisId == kCigIdMax) { + BTIF_TRACE_ERROR( + "%s: cannot allocate unique CIS ID to = %s ", + __func__, peer_address.ToString().c_str()); + return nullptr; + } + cig_cis_pair.insert(std::make_pair(peer_address, map())); + cig_cis_pair[peer_address].insert(std::make_pair(CigId, CisId)); + + LOG_INFO(LOG_TAG, + "%s: Create peer: peer_address=%s, set_id=%d, cig_id=%d, cis_id=%d", + __PRETTY_FUNCTION__, peer_address.ToString().c_str(), SetId, CigId, CisId); + peer = new BtifAcmPeer(peer_address, ACM_TSEP_SNK, SetId, CigId, CisId); + peer->SetPeerVoiceTxState(StreamState::DISCONNECTED); + peer->SetPeerVoiceRxState(StreamState::DISCONNECTED); + peer->SetPeerMusicTxState(StreamState::DISCONNECTED); + peer->SetPeerMusicRxState(StreamState::DISCONNECTED); + if (SetId >= kPeerMinSetId && SetId < kPeerMaxSetId) { + LOG_INFO(LOG_TAG, + "%s: Created peer is TWM device",__PRETTY_FUNCTION__); + peer->SetIsStereoHsType(true); + } + peers_.insert(std::make_pair(peer_address, peer)); + peer->Init(); + return peer; +} + +BtifAcmPeer* BtifAcmInitiator::FindMusicActivePeer() { + for (auto it : peers_) { + BtifAcmPeer* peer = it.second; + if (peer->IsPeerActiveForMusic()) { + return peer; + } + } + return nullptr; +} + +bool BtifAcmInitiator::AllowedToConnect(const RawAddress& peer_address) const { + int connected = 0; + + // Count peers that are in the process of connecting or already connected + for (auto it : peers_) { + const BtifAcmPeer* peer = it.second; + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + if (peer->PeerAddress() == peer_address) { + return true; // Already connected or accounted for + } + connected++; + break; + default: + break; + } + } + return (connected < max_connected_peers_); +} + +bool BtifAcmInitiator::IsAcmIdle() const { + int connected = 0; + + // Count peers that are in the process of connecting or already connected + for (auto it : peers_) { + const BtifAcmPeer* peer = it.second; + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + case BtifAcmStateMachine::kStateClosing: + connected++; + break; + default: + break; + } + } + return (connected == 0); +} + +bool BtifAcmInitiator::IsSetIdle(uint8_t setId) const { + int connected = 0; + tBTA_CSIP_CSET cset_info = BTA_CsipGetCoordinatedSet(setId); + std::vector::iterator itr; + if ((cset_info.set_members).size() > 0) { + for (itr = (cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + case BtifAcmStateMachine::kStateClosing: + connected++; + break; + default: + break; + } + } + } + return (connected == 0); +} + +bool BtifAcmInitiator::IsOtherSetPeersIdle(const RawAddress& peer_address, uint8_t setId) const { + int connected = 0; + tBTA_CSIP_CSET cset_info = BTA_CsipGetCoordinatedSet(setId); + std::vector::iterator itr; + if ((cset_info.set_members).size() > 0) { + for (itr = (cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + if (*itr == peer_address) continue; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer == nullptr) continue; + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + case BtifAcmStateMachine::kStateClosing: + connected++; + break; + default: + break; + } + } + } + return (connected == 0); +} + +bool BtifAcmInitiator::DeletePeer(const RawAddress& peer_address) { + auto it = peers_.find(peer_address); + if (it == peers_.end()) return false; + BtifAcmPeer* peer = it->second; + for (auto itr = addr_setid_pair.begin(); itr != addr_setid_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + addr_setid_pair.erase(itr); + break; + } + } + for (auto itr = set_cig_pair.begin(); itr != set_cig_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + set_cig_pair.erase(itr); + break; + } + } + bool found = false; + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == peer->CigId()) { + if (ptr->second == peer->CisId()) { + cig_cis_pair.erase(itr); + found = true; + break; + } + } + } + if (found) + break; + } + peer->Cleanup(); + peers_.erase(it); + delete peer; + return true; +} + +void BtifAcmInitiator::DeleteIdlePeers() { + for (auto it = peers_.begin(); it != peers_.end();) { + BtifAcmPeer* peer = it->second; + auto prev_it = it++; + if (!peer->CanBeDeleted()) continue; + LOG_INFO(LOG_TAG, "%s: Deleting idle peer: %s ", __func__, + peer->PeerAddress().ToString().c_str()); + for (auto itr = addr_setid_pair.begin(); itr != addr_setid_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + addr_setid_pair.erase(itr); + break; + } + } + for (auto itr = set_cig_pair.begin(); itr != set_cig_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + set_cig_pair.erase(itr); + break; + } + } + bool found = false; + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == peer->CigId()) { + if (ptr->second == peer->CisId()) { + cig_cis_pair.erase(itr); + found = true; + break; + } + } + } + if (found) + break; + } + peer->Cleanup(); + peers_.erase(prev_it); + delete peer; + } +} + +void BtifAcmInitiator::CleanupAllPeers() { + while (!peers_.empty()) { + auto it = peers_.begin(); + BtifAcmPeer* peer = it->second; + for (auto itr = addr_setid_pair.begin(); itr != addr_setid_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + addr_setid_pair.erase(itr); + break; + } + } + for (auto itr = set_cig_pair.begin(); itr != set_cig_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + set_cig_pair.erase(itr); + break; + } + } + bool found = false; + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == peer->CigId()) { + if (ptr->second == peer->CisId()) { + cig_cis_pair.erase(itr); + found = true; + break; + } + } + } + if (found) + break; + } + peer->Cleanup(); + peers_.erase(it); + delete peer; + } +} + +class UcastClientCallbacksImpl : public UcastClientCallbacks { + public: + ~UcastClientCallbacksImpl() = default; + void OnStreamState(const RawAddress& address, + std::vector streams_state_info) override { + LOG(INFO) << __func__; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(address); + if (peer == nullptr) { + BTIF_TRACE_DEBUG("%s: Peer is NULL", __PRETTY_FUNCTION__); + } + for (auto it = streams_state_info.begin(); it != streams_state_info.end(); ++it) { + LOG(WARNING) << __func__ << ": address: " << address; + LOG(WARNING) << __func__ << ": stream type: " + << GetStreamType(it->stream_type.type); + LOG(WARNING) << __func__ << ": stream context: " + << GetStreamType(it->stream_type.audio_context); + LOG(WARNING) << __func__ << ": stream dir: " + << GetStreamDirection(it->stream_type.direction); + LOG(WARNING) << __func__ << ": stream state: " + << GetStreamState(static_cast (it->stream_state)); + switch (it->stream_state) { + case StreamState::DISCONNECTED: + case StreamState::DISCONNECTING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_DISCONNECT_EVT, (char*)&data); + } break; + + case StreamState::CONNECTING: + case StreamState::CONNECTED: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_CONNECT_EVT, (char*)&data); + } break; + + case StreamState::STARTING: + case StreamState::STREAMING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_START_EVT, (char*)&data); + } break; + + case StreamState::STOPPING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_STOP_EVT, (char*)&data); + } break; + + case StreamState::RECONFIGURING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_RECONFIG_EVT, (char*)&data); + } break; + default: + break; + } + } + } + + void OnStreamConfig(const RawAddress& address, + std::vector streams_config_info) override { + LOG(INFO) << __func__; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(address); + if (peer == nullptr) { + BTIF_TRACE_DEBUG("%s: Peer is NULL", __PRETTY_FUNCTION__); + } + for (auto it = streams_config_info.begin(); it != streams_config_info.end(); ++it) { + tBTA_ACM_CONFIG_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .codec_config = it->codec_config, .audio_location = it->audio_location, + .qos_config = it->qos_config, .codecs_selectable = it->codecs_selectable}; + btif_acm_handle_evt(BTA_ACM_CONFIG_EVT, (char*)&data); + } + } + + void OnStreamAvailable(const RawAddress& bd_addr, uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + LOG(INFO) << __func__; + //Need to use during START of src and sink audio context + BTIF_TRACE_DEBUG("%s: Peer %s, src_audio_context: 0x%x, sink_audio_contexts: 0x%x", + __func__, + bd_addr.ToString().c_str(), src_audio_contexts, sink_audio_contexts); + } + + const char* GetStreamType(uint16_t stream_type) { + switch (stream_type) { + CASE_RETURN_STR(CONTENT_TYPE_UNSPECIFIED) + CASE_RETURN_STR(CONTENT_TYPE_CONVERSATIONAL) + CASE_RETURN_STR(CONTENT_TYPE_MEDIA) + CASE_RETURN_STR(CONTENT_TYPE_INSTRUCTIONAL) + CASE_RETURN_STR(CONTENT_TYPE_NOTIFICATIONS) + CASE_RETURN_STR(CONTENT_TYPE_ALERT) + CASE_RETURN_STR(CONTENT_TYPE_MAN_MACHINE) + CASE_RETURN_STR(CONTENT_TYPE_EMERGENCY) + CASE_RETURN_STR(CONTENT_TYPE_RINGTONE) + CASE_RETURN_STR(CONTENT_TYPE_SOUND_EFFECTS) + CASE_RETURN_STR(CONTENT_TYPE_LIVE) + CASE_RETURN_STR(CONTENT_TYPE_GAME) + default: + return "Unknown StreamType"; + } + } + + const char* GetStreamDirection(uint8_t event) { + switch (event) { + CASE_RETURN_STR(ASE_DIRECTION_SINK) + CASE_RETURN_STR(ASE_DIRECTION_SRC) + default: + return "Unknown StreamDirection"; + } + } + + const char* GetStreamState(uint8_t event) { + switch (event) { + CASE_RETURN_STR(STREAM_STATE_DISCONNECTED) + CASE_RETURN_STR(STREAM_STATE_CONNECTING) + CASE_RETURN_STR(STREAM_STATE_CONNECTED) + CASE_RETURN_STR(STREAM_STATE_STARTING) + CASE_RETURN_STR(STREAM_STATE_STREAMING) + CASE_RETURN_STR(STREAM_STATE_STOPPING) + CASE_RETURN_STR(STREAM_STATE_DISCONNECTING) + CASE_RETURN_STR(STREAM_STATE_RECONFIGURING) + default: + return "Unknown StreamState"; + } + } +}; + +static UcastClientCallbacksImpl sUcastClientCallbacks; + +bt_status_t BtifAcmInitiator::Init( + btacm_initiator_callbacks_t* callbacks, int max_connected_acceptors, + const std::vector& codec_priorities) { + LOG_INFO(LOG_TAG, "%s: max_connected_acceptors=%d", __PRETTY_FUNCTION__, + max_connected_acceptors); + if (enabled_) return BT_STATUS_SUCCESS; + CleanupAllPeers(); + max_connected_peers_ = max_connected_acceptors; + alarm_free(music_set_lock_release_timer_); + alarm_free(voice_set_lock_release_timer_); + alarm_free(acm_group_procedure_timer_); + alarm_free(acm_conn_interval_timer_); + music_set_lock_release_timer_ = alarm_new("btif_acm_initiator.music_set_lock_release_timer"); + voice_set_lock_release_timer_ = alarm_new("btif_acm_initiator.voice_set_lock_release_timer"); + acm_group_procedure_timer_ = alarm_new("btif_acm_initiator.acm_group_procedure_timer"); + acm_conn_interval_timer_ = alarm_new("btif_acm_initiator.acm_conn_interval_timer"); + + callbacks_ = callbacks; + //init local capabilties + init_local_capabilities(); + + // register ACM with AHIM + btif_register_cb(); + + btif_vmcp_init(); + bt_status_t status1 = btif_acm_initiator_execute_service(true); + if (status1 == BT_STATUS_SUCCESS) { + BTIF_TRACE_EVENT("%s: status success", __func__); + } + if (sUcastClientInterface != nullptr) { + LOG_INFO(LOG_TAG, "%s Cleaning up BAP client Interface before initializing...", + __PRETTY_FUNCTION__); + sUcastClientInterface->Cleanup(); + sUcastClientInterface = nullptr; + } + sUcastClientInterface = bluetooth::bap::ucast::btif_bap_uclient_get_interface(); + + + if (sUcastClientInterface == nullptr) { + LOG_ERROR(LOG_TAG, "%s Failed to get BAP Interface", __PRETTY_FUNCTION__); + return BT_STATUS_FAIL; + } + char value[PROPERTY_VALUE_MAX]; + if(property_get("persist.vendor.service.bt.bap.conn_update", value, "false") + && !strcmp(value, "true")) { + is_conn_update_enabled_ = true; + } else { + is_conn_update_enabled_ = false; + } + sUcastClientInterface->Init(&sUcastClientCallbacks); + enabled_ = true; + return BT_STATUS_SUCCESS; +} + +void BtifAcmStateMachine::StateIdle::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + if(btif_acm_initiator.IsConnUpdateEnabled()) { + if ((peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateOpened) || + (peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateStarted)) + { + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + } else { + LOG_ERROR(LOG_TAG, "%s Already in relaxed intervals", __PRETTY_FUNCTION__); + } + } else if (peer_.StateMachine().PreviousStateId() != BtifAcmStateMachine::kStateInvalid) { + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + } + } + peer_.ClearConnUpdateMode(); + peer_.ClearAllFlags(); + peer_.SetProfileType(0); + peer_.SetRcfgProfileType(0); + memset(¤t_media_config, 0, sizeof(current_media_config)); + + // Delete peers that are re-entering the Idle state + if (peer_.IsAcceptor()) { + do_in_bta_thread(FROM_HERE, base::Bind(&BtifAcmInitiator::DeleteIdlePeers, + base::Unretained(&btif_acm_initiator))); + } +} + +void BtifAcmStateMachine::StateIdle::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + break; +#if 0 + case BTIF_ACM_DISCONNECT_REQ_EVT: { + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (peer_.GetProfileType() & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (peer_.GetProfileType() & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_2); + disconnect_streams.push_back(type_3); + StreamType type_1; + if (peer_.GetProfileType() & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (peer_.GetProfileType() & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + // Re-enter Idle so the peer can be deleted + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } + break; +#endif + + case BTIF_ACM_CONNECT_REQ_EVT: { + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + bool can_connect = true; + // Check whether connection is allowed + if (peer_.IsAcceptor()) { + //There is no char in current spec. Should we check VMCP role here? + // shall we assume VMCP role would have been checked in apps and no need to check here? + can_connect = btif_acm_initiator.AllowedToConnect(peer_.PeerAddress()); + if (!can_connect) disconnect_acm_initiator(peer_.PeerAddress(), p_bta_data->contextType); + } + if (!can_connect) { + BTIF_TRACE_ERROR( + "%s: Cannot connect to peer %s: too many connected " + "peers", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + break; + } + std::vector streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamConnect conn_media; + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); +#if 0 + if (false) {//enable when GCP support is available + SelectCodecQosConfig(peer_.PeerAddress(), (peer_.GetProfileType() & ~WMCP), VOICE_CONTEXT, SRC, EB_CONFIG); + StreamConnect conn_voice; + CodecQosConfig config; + conn_voice.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + config = peer_.get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(config.codec_config); + print_qos_parameters(config.qos_config); + conn_voice.stream_type.direction = ASE_DIRECTION_SRC; + conn_voice.codec_qos_config_pair.push_back(config); + streams.push_back(conn_voice); + } +#endif + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamConnect conn_media, conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamConnect conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + } + LOG(WARNING) << __func__ << " size of streams " << streams.size(); + if (!sUcastClientInterface) break; + // intiate background connection + std::vector address; + address.push_back(peer_.PeerAddress()); + sUcastClientInterface->Connect(address, false, streams); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpening); + } break; +#if 0 + case BTA_ACM_DISCONNECT_EVT: { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + BTIF_TRACE_DEBUG("%s: received Media Rx disconnected state from BAP, set state & ignore", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received Media disconnecting state from BAP, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + } + } break; +#endif + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + + return true; +} + +void BtifAcmStateMachine::StateOpening::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + +} + +void BtifAcmStateMachine::StateOpening::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateOpening::ProcessEvent(uint32_t event, void* p_data) { + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + break; // Ignore + + case BTA_ACM_CONNECT_EVT: { + tBTIF_ACM* p_bta_data = (tBTIF_ACM*)p_data; + btacm_connection_state_t state; + uint8_t status = (uint8_t)p_bta_data->state_info.stream_state; + uint16_t contextType = p_bta_data->state_info.stream_type.type; + + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s flags=%s status=%d contextType=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), peer_.FlagsToString().c_str(), + status, contextType); + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_bta_data->state_info.stream_state == StreamState::CONNECTED) { + state = BTACM_CONNECTION_STATE_CONNECTED; + // Report the connection state to the application + btif_report_connection_state(peer_.PeerAddress(), state, CONTEXT_TYPE_MUSIC); + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_bta_data->state_info.stream_state); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for mediaTx, move in opened state", __func__); + } else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_bta_data->state_info.stream_state); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for mediaRx, move in opened state", __func__); + } + // Change state to OPENED + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else if (p_bta_data->state_info.stream_state == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for MEDIA Tx or Rx, ignore", __func__); + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) + peer_.SetPeerMusicTxState(p_bta_data->state_info.stream_state); + else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_bta_data->state_info.stream_state); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + if (p_bta_data->state_info.stream_state == StreamState::CONNECTED) { + state = BTACM_CONNECTION_STATE_CONNECTED; + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_bta_data->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), state, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Tx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_bta_data->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), state, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Rx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } + } else if (p_bta_data->state_info.stream_state == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for CONVERSATIONAL Tx or Rx, ignore", __func__); + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_bta_data->state_info.stream_state); + } else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_bta_data->state_info.stream_state); + } + } + } + } break; + + case BTA_ACM_DISCONNECT_EVT: { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connecting, remain in opening state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else if (peer_.GetPeerMusicTxState() == StreamState::CONNECTING || + peer_.GetPeerMusicRxState() == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but either music Tx or Rx still connecting," + " remain in opening state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else if (peer_.GetPeerMusicTxState() == StreamState::CONNECTING || + peer_.GetPeerMusicRxState() == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconnected but music Tx or Rx still connecting," + " remain in opening state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for MEDIA Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for CONVERSATIONAL Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + } + } + } + break; + + case BTIF_ACM_DISCONNECT_REQ_EVT:{ + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + if ((p_bta_data->contextType == CONTEXT_TYPE_MUSIC) && ((peer_.GetPeerVoiceRxState() == StreamState::CONNECTING) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTING))) { + LOG(WARNING) << __func__ << " voice connecting remain in opening "; + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } else if ((p_bta_data->contextType == CONTEXT_TYPE_VOICE) && (peer_.GetPeerMusicTxState() == StreamState::CONNECTING || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTING))) { + LOG(WARNING) << __func__ << " Music connecting remain in opening "; + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + } else { + LOG(WARNING) << __func__ << " Move in idle state "; + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC_VOICE); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } + } + break; + + case BTIF_ACM_CONNECT_REQ_EVT: { + BTIF_TRACE_WARNING( + "%s: Peer %s : event=%s : device is already connecting, " + "ignore Connect request", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + } break; + + case BTA_ACM_CONFIG_EVT: { + tBTIF_ACM* p_acm_data = (tBTIF_ACM*)p_data; + uint16_t contextType = p_acm_data->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare with current media config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm_data->config_info.codec_config); + } else if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm_data->config_info.codec_config; + } + if (mandatory_codec_selected) { + BTIF_TRACE_DEBUG("%s: Mandatory codec selected, do not store config", __PRETTY_FUNCTION__); + } else { + BTIF_TRACE_DEBUG("%s: store configuration", __PRETTY_FUNCTION__); + } + //Cache the peer latency in WMCP case + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL && + p_acm_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config", __PRETTY_FUNCTION__); + current_voice_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_voice_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_VOICE); + } + //Handle BAP START if reconfig comes in mid of streaming + //peer_.SetStreamReconfigInfo(p_acm->acm_reconfig); + //TODO: local capabilities + //CodecConfig record = p_bta_data->acm_reconfig.codec_config; + //saving codec config as negotiated parameter as true + //btif_pacs_add_record(peer_.PeerAddress(), true, CodecDirection::CODEC_DIR_SRC, &record); + + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + return true; +} + +bool btif_peer_device_is_streaming(uint8_t Id) { + bool is_streaming = false; + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(Id); + if (cset_info.size == 0) { + BTIF_TRACE_ERROR("%s: CSET info size is zero, return", __func__); + return false; + } + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer != nullptr && (peer->IsStreaming() || peer->CheckFlags(BtifAcmPeer::kFlagPendingStart)) && + !peer->CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + BTIF_TRACE_DEBUG("%s: fellow device is streaming %s ", __func__, peer->PeerAddress().ToString().c_str()); + is_streaming = true; + break; + } + } + } + return is_streaming; +} + +bool btif_peer_device_is_reconfiguring(uint8_t Id) { + bool is_reconfigured = false; + if (Id < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(Id); + if (cset_info.size == 0) { + BTIF_TRACE_ERROR("%s: CSET info size is zero, return", __func__); + return false; + } + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer != nullptr && peer->CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { + BTIF_TRACE_DEBUG("%s: peer is reconfiguring %s ", __func__, peer->PeerAddress().ToString().c_str()); + is_reconfigured = true; + break; + } + } + } + } else { + is_reconfigured = true; + BTIF_TRACE_ERROR("%s: peer is TWM device, return is_reconfigured %d", __func__, is_reconfigured); + } + return is_reconfigured; +} + +void BtifAcmStateMachine::StateOpened::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s, Peer SetId = %d, MusicActiveSetId = %d, ContextType = %d", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), peer_.SetId(), + btif_acm_initiator.MusicActiveCSetId(), peer_.GetContextType()); + + //Starting the timer for 5 seconds before moving to relaxed state as + //stop event or start streaming event moght immediately come + //which requires aggresive interval + if(btif_acm_initiator.IsConnUpdateEnabled()) { + btif_acm_check_and_start_conn_Interval_timer(&peer_); + } + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend | + BtifAcmPeer::kFlagPendingStart | + BtifAcmPeer::kFlagPendingStop); + + BTIF_TRACE_DEBUG("%s: kFlagPendingReconfigure %d and kFLagPendingStartAfterReconfig %d", __PRETTY_FUNCTION__, + peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure), + peer_.CheckFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig)); + + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingReconfigure); + if ((peer_.GetRcfgProfileType() != BAP_CALL) && + (current_active_profile_type != peer_.GetRcfgProfileType())) { + current_active_profile_type = peer_.GetRcfgProfileType(); + if (current_active_profile_type != WMCP) + current_active_config = current_media_config; + else + current_active_config = current_recording_config; + + if (btif_peer_device_is_reconfiguring(peer_.SetId())) + btif_acm_source_restart_session(active_bda, active_bda); + + if (current_active_profile_type == WMCP) { + uint16_t sink_latency = btif_acm_get_active_device_latency(); + BTIF_TRACE_EVENT("%s: sink_latency = %dms", __func__, sink_latency); + if ((sink_latency > 0) && !btif_acm_update_sink_latency_change(sink_latency * 10)) { + BTIF_TRACE_ERROR("%s: unable to update latency", __func__); + } + } + if (current_active_profile_type == BAP) { + peer_.ResetProfileType(GCP); + peer_.SetProfileType(BAP); + } else if (current_active_profile_type == GCP) { + peer_.ResetProfileType(BAP); + peer_.SetProfileType(GCP); + } + BTIF_TRACE_DEBUG("%s: cummulative_profile_type %d", __func__, peer_.GetProfileType()); + BTIF_TRACE_DEBUG("%s: Reconfig + restart session completed for media, signal session ready", __func__); + btif_acm_signal_session_ready(); + } else if (current_active_profile_type == peer_.GetRcfgProfileType()) { + BTIF_TRACE_DEBUG("%s: Reconfig to remote is completed for media, restart session wasn't needed", __func__); + } else { + BTIF_TRACE_DEBUG("%s: Reconfig completed for BAP_CALL", __func__); + } + } + //Start the lock release timer here. + //check if peer device is in started state + if (btif_peer_device_is_streaming(peer_.SetId()) || + peer_.CheckFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig)) { + StreamType type_1, type_2; + std::vector start_streams; + if (peer_.GetRcfgProfileType() != BAP_CALL) { + if ((current_active_profile_type == BAP || current_active_profile_type == GCP) && + (peer_.GetPeerMusicTxState() == StreamState::CONNECTED)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + start_streams.push_back(type_1); + } else if ((current_active_profile_type == WMCP) && + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + } + } else { + if ((peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) && + (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED)) { + type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + start_streams.push_back(type_2); + } + } + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + sUcastClientInterface->Start(peer_.PeerAddress(), start_streams); + peer_.SetFlags(BtifAcmPeer::kFlagPendingStart); + peer_.ClearFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig); + } + peer_.SetRcfgProfileType(0); + + if (peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateStarted) { + BTIF_TRACE_DEBUG("%s: Entering Opened from Started State", __PRETTY_FUNCTION__); + if ((btif_acm_initiator.GetGroupLockStatus(peer_.SetId()) != + BtifAcmInitiator::kFlagStatusUnknown) && + alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + BTIF_TRACE_DEBUG("%s: All locked and stop/suspend requested device have stopped, ack mm audio", __func__); + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + } + + if (peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateStarted) { + if ((btif_acm_initiator.MusicActiveCSetId() > 0) && + (btif_acm_initiator.GetGroupLockStatus(btif_acm_initiator.MusicActiveCSetId()) == BtifAcmInitiator::kFlagStatusLocked)) { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + } +} + +void BtifAcmStateMachine::StateOpened::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + + peer_.ClearFlags(BtifAcmPeer::kFlagPendingStart); +} + +bool BtifAcmStateMachine::StateOpened::ProcessEvent(uint32_t event, + void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_CONNECT_REQ_EVT: { + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + bool can_connect = true; + // Check whether connection is allowed + if (peer_.IsAcceptor()) { + //There is no char in current spec. Should we check VMCP role here? + // shall we assume VMCP role would have been checked in apps and no need to check here? + can_connect = btif_acm_initiator.AllowedToConnect(peer_.PeerAddress()); + if (!can_connect) disconnect_acm_initiator(peer_.PeerAddress(), p_bta_data->contextType); + } + if (!can_connect) { + BTIF_TRACE_ERROR( + "%s: Cannot connect to peer %s: too many connected " + "peers", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + break; + } + std::vector streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamConnect conn_media; + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); +#if 0 + if (false) {//enable when GCP support is available + SelectCodecQosConfig(peer_.PeerAddress(), (peer_.GetProfileType() & ~WMCP), VOICE_CONTEXT, SRC, EB_CONFIG); + StreamConnect conn_voice; + CodecQosConfig config; + conn_voice.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + config = peer_.get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(config.codec_config); + print_qos_parameters(config.qos_config); + conn_voice.stream_type.direction = ASE_DIRECTION_SRC; + conn_voice.codec_qos_config_pair.push_back(config); + streams.push_back(conn_voice); + } +#endif + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamConnect conn_media, conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamConnect conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + } + LOG(WARNING) << __func__ << " size of streams " << streams.size(); + if (!sUcastClientInterface) break; + std::vector address; + address.push_back(peer_.PeerAddress()); + sUcastClientInterface->Connect(address, false, streams); + //peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpening); + } break; + + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: { + BTIF_TRACE_DEBUG("%s: Already in OPENED state, ACK success", __PRETTY_FUNCTION__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } break; + + case BTIF_ACM_START_STREAM_REQ_EVT: { + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str()); + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingStart)) { + BTIF_TRACE_DEBUG("%s: Ignore Start req", __PRETTY_FUNCTION__); + break; + } +#if 0 + //Can be either music or voice, prior to coming here, + //this must have been evaluated for locking logic + grp logic + StreamType type_1; + std::vector start_streams; + if (current_active_profile_type != WMCP) { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + StreamType type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + start_streams.push_back(type_1); + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + StreamType type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + start_streams.push_back(type_2); + LOG_INFO(LOG_TAG, "%s: sending start for voice###", __PRETTY_FUNCTION__); + } + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + } + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + if (!sUcastClientInterface) break; + sUcastClientInterface->Start(peer_.PeerAddress(), start_streams); +#endif +#if 1 + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + reconfig_acm_initiator(peer_.PeerAddress(), current_active_profile_type); + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + reconfig_acm_initiator(peer_.PeerAddress(), BAP_CALL); + } + peer_.SetFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig); +#endif + } + break; + + case BTA_ACM_START_EVT: { + tBTIF_ACM_STATUS status = (uint8_t)p_acm->state_info.stream_state; + //int contextType = p_acm->state_info.stream_type.type; + LOG_INFO(LOG_TAG, + "%s: Peer %s : event=%s status=%d ", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(),status); + + if (p_acm->state_info.stream_state == StreamState::STARTING) { + //Check what to do in this case + BTIF_TRACE_DEBUG("%s: BAP returned as starting, ignore", __PRETTY_FUNCTION__); + break; + } else if (p_acm->state_info.stream_state == StreamState::STREAMING){ + peer_.ClearFlags(BtifAcmPeer::kFlagPendingStart); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateStarted); + } + } break; + + case BTIF_ACM_DISCONNECT_REQ_EVT:{ + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingStart)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingStart); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } + } + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + if ((p_bta_data->contextType == CONTEXT_TYPE_MUSIC) && ((peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " voice connected remain in opened "; + } else if ((p_bta_data->contextType == CONTEXT_TYPE_VOICE) && ((peer_.GetPeerMusicTxState() == StreamState::CONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " Music connected remain in opened "; + } else { + LOG(WARNING) << __func__ << " Move in closing state "; + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } + } break; + + case BTA_ACM_STOP_EVT: { //Sumit: what is this case? + int contextType = p_acm->acm_connect.streams_info.stream_type.type; + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, peer_.GetStreamContextType()); + if (contextType == CONTENT_TYPE_MEDIA) + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } break; + + case BTA_ACM_CONNECT_EVT: {// above evnt can come and handle for voice/media case + tBTIF_ACM_STATUS status = (uint8_t)p_acm->state_info.stream_state; + int contextType = p_acm->state_info.stream_type.type; + + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s status=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(),status); + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + BTIF_TRACE_DEBUG("%s: peer device is suspended, send MM any pending ACK", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + break; + } + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + if (contextType == CONTENT_TYPE_MEDIA) { + if ((btif_acm_initiator.MusicActivePeer() == peer_.PeerAddress()) && + peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { //recheck + LOG(INFO) << __PRETTY_FUNCTION__ << " : Peer " << peer_.PeerAddress() + << " : Reconfig done - calling startSession() to audio HAL"; + std::promise peer_ready_promise; + std::future peer_ready_future = peer_ready_promise.get_future(); + //TODO: cannot use peer addr here, must need group address. + btif_acm_source_start_session(peer_.PeerAddress()); + //Perform group operation here + } else if (((peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED)) && + (peer_.GetPeerMusicTxState() == StreamState::CONNECTING) && + (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK)) { + BTIF_TRACE_DEBUG("%s: music Tx connected when either Voice Tx/Rx or Music Rx was connected," + "remain in opened state", __func__); + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music TX, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_MUSIC); + } else if ((peer_.GetPeerMusicRxState() == StreamState::CONNECTING) && + (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC)) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music RX(recording), update state", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_MUSIC); + } +#if 0 + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingStart)) { + LOG(INFO) << __PRETTY_FUNCTION__ << " : Peer " << peer_.PeerAddress() + << " : Reconfig done - calling BTA_AvStart()"; + StreamType type_1; + if (current_active_profile_type != WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + } + std::vector start_streams; + start_streams.push_back(type_1); + if (!sUcastClientInterface) break; + sUcastClientInterface->Start(peer_.PeerAddress(), start_streams); + } +#endif + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: voice context connected, remain in opened state" + " peer_.GetPeerVoiceTxState() %d peer_.GetPeerVoiceRxState() %d", + __func__, peer_.GetPeerVoiceTxState(), peer_.GetPeerVoiceRxState()); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK && + (peer_.GetPeerVoiceTxState() != StreamState::CONNECTED)) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice TX, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC && + (peer_.GetPeerVoiceRxState() != StreamState::CONNECTED)) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice RX, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING){ + if (contextType == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for MEDIA Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for CONVERSATIONAL Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + } + } + } break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in opened state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in opened state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in opened state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + if ((peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING)) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when Voice Tx+Rx and Media Rx/Tx disconnected/ing, move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in opened state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in opened state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in opened state", __func__); + } + } + } + } + } + } + break; + + case BTIF_ACM_RECONFIG_REQ_EVT: { + std::vector reconf_streams; + StreamReconfig reconf_info; + CodecQosConfig cfg; + if (p_acm->acm_reconfig.streams_info.stream_type.type != CONTENT_TYPE_CONVERSATIONAL) { + reconf_info.stream_type.type = p_acm->acm_reconfig.streams_info.stream_type.type; + reconf_info.stream_type.audio_context = + p_acm->acm_reconfig.streams_info.stream_type.audio_context; + reconf_info.stream_type.direction = p_acm->acm_reconfig.streams_info.stream_type.direction; + reconf_info.reconf_type = p_acm->acm_reconfig.streams_info.reconf_type; + cfg = peer_.get_peer_media_codec_qos_config(); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + } else { + reconf_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer_.IsStereoHsType()) { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, EB_CONFIG); + } + cfg = peer_.get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(cfg.codec_config); + print_qos_parameters(cfg.qos_config); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + + reconf_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer_.IsStereoHsType()) { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, EB_CONFIG); + } + cfg = peer_.get_peer_voice_tx_codec_qos_config(); + print_codec_parameters(cfg.codec_config); + print_qos_parameters(cfg.qos_config); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + + peer_.SetPeerVoiceRxState(StreamState::RECONFIGURING); + peer_.SetPeerVoiceTxState(StreamState::RECONFIGURING); + } + if (!sUcastClientInterface) break; + sUcastClientInterface->Reconfigure(peer_.PeerAddress(), reconf_streams); + peer_.SetFlags(BtifAcmPeer::kFlagPendingReconfigure); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateReconfiguring); + } + break; + case BTA_ACM_CONFIG_EVT: { + tBTIF_ACM* p_acm_data = (tBTIF_ACM*)p_data; + uint16_t contextType = p_acm_data->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare with current media config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm_data->config_info.codec_config); + } else if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm_data->config_info.codec_config; + } + if (mandatory_codec_selected) { + BTIF_TRACE_DEBUG("%s: Mandatory codec selected, do not store config", __PRETTY_FUNCTION__); + } else { + BTIF_TRACE_DEBUG("%s: store configuration", __PRETTY_FUNCTION__); + } + //Cache the peer latency in WMCP case + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL && + p_acm_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config", __PRETTY_FUNCTION__); + current_voice_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_voice_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_VOICE); + } + //Handle BAP START if reconfig comes in mid of streaming + //peer_.SetStreamReconfigInfo(p_acm->acm_reconfig); + //TODO: local capabilities + //CodecConfig record = p_bta_data->acm_reconfig.codec_config; + //saving codec config as negotiated parameter as true + //btif_pacs_add_record(peer_.PeerAddress(), true, CodecDirection::CODEC_DIR_SRC, &record); + + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + return true; +} + +bool btif_acm_check_if_requested_devices_started() { + std::vector::iterator itr; + if ((btif_acm_initiator.locked_devices).size() > 0) { + for (itr = (btif_acm_initiator.locked_devices).begin(); itr != (btif_acm_initiator.locked_devices).end(); itr++) { + BTIF_TRACE_DEBUG("%s: address =%s", __func__, *itr->ToString().c_str()); + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if ((peer == nullptr) || (peer != nullptr && !peer->IsStreaming())) { + break; + } + } + if (itr == (btif_acm_initiator.locked_devices).end()) { + return true; + } + } + return false; +} + +bool btif_acm_check_if_requested_devices_stopped() { + std::vector::iterator itr; + if ((btif_acm_initiator.locked_devices).size() > 0) { + for (itr = (btif_acm_initiator.locked_devices).begin(); itr != (btif_acm_initiator.locked_devices).end(); itr++) { + BTIF_TRACE_DEBUG("%s: address =%s", __func__, *itr->ToString().c_str()); + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if ((peer == nullptr) || (peer != nullptr /*&& !peer->IsSuspended()*/)) { + break; + } + } + if (itr == (btif_acm_initiator.locked_devices).end()) { + return true; + } + } + return false; +} + +void BtifAcmStateMachine::StateStarted::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s, Peer SetId = %d, MusicActiveSetId = %d, ContextType = %d", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + peer_.SetId(), btif_acm_initiator.MusicActiveCSetId(), peer_.GetContextType()); + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Starting the timer for 5 seconds before moving to relaxed state as + //stop event or start streaming event moght immediately come + //which requires aggresive interval + btif_acm_check_and_start_conn_Interval_timer(&peer_); + } + + // Report that we have entered the Streaming stage. Usually, this should + // be followed by focus grant. See update_audio_focus_state() + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STARTED, peer_.GetStreamContextType()); + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } else { + BTIF_TRACE_DEBUG("%s:no group procedure timer running ACK pending cmd", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } +#if 0 + if ((btif_acm_initiator.GetGroupLockStatus(peer_.SetId()) != BtifAcmInitiator::kFlagStatusUnknown) && + alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + BTIF_TRACE_DEBUG("%s: All locked and start requested device have started, ack mm audio", __func__); + //in this case, we need to change channel mode to stereo + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + + //Start the lock release timer here. + if ((btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID) && + (btif_acm_initiator.GetGroupLockStatus(btif_acm_initiator.MusicActiveCSetId()) == BtifAcmInitiator::kFlagStatusLocked)) { + BTIF_TRACE_DEBUG("%s: ", __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + if (!btif_acm_initiator.IsMusicActiveGroupStarted()) { + if (peer_.SetId() == btif_acm_initiator.MusicActiveCSetId()) + btif_acm_initiator.SetMusicActiveGroupStarted(true); + } +#endif + +} + +void BtifAcmStateMachine::StateStarted::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateStarted::ProcessEvent(uint32_t event, void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: { + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str()); + peer_.SetFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + + StreamType type_1; + std::vector stop_streams; + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + if (current_active_profile_type != WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + stop_streams.push_back(type_1); + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_1); + } + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + StreamType type_2; + type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_2); + stop_streams.push_back(type_1); + } + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + + if (!sUcastClientInterface) break; + sUcastClientInterface->Stop(peer_.PeerAddress(), stop_streams); + } + break; + + case BTIF_ACM_DISCONNECT_REQ_EVT: { + int contextType = p_acm->state_info.stream_type.type; + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s contextType=%d", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), contextType); + + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + // Inform the application that we are disconnecting + if ((p_bta_data->contextType == CONTEXT_TYPE_MUSIC) && ((peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " voice connected move in opened state "; + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else if ((p_bta_data->contextType == CONTEXT_TYPE_VOICE) && ((peer_.GetPeerMusicTxState() == StreamState::CONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " Music connected remain in started state "; + } else { + LOG(WARNING) << __func__ << " Move in closing state "; + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } + } + break; + + case BTA_ACM_STOP_EVT: { + int contextType = p_acm->state_info.stream_type.type; + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str()); + if (contextType == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: STOPPING event came from BAP for Media, ignore", __func__); + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: STOPPING event came from BAP for Voice, ignore", __func__); + } + } + break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in started state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in started state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in started state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if ((peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING)) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when Voice Tx+Rx and Media Rx/Tx disconnected/ing, move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + std::vector disconnect_streams; + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, CONTEXT_TYPE_MUSIC); + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP while streaming" + " when either Voice Tx or Rx or Media Rx/Tx is connected, move to opened state", __func__); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) { + BTIF_TRACE_DEBUG("%s: Received disconnecting for Music-Tx, initiate for Rx also", __func__); + StreamType type_1; + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + } + if (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING) { + BTIF_TRACE_DEBUG("%s: Received disconnecting for Music-Rx, initiate for Tx also", __func__); + StreamType type_1; + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + } + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in started state", __func__); + } + } + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx while streaming," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " move to opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in started state", __func__); + } + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx while streaming," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " move to Opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in started state", __func__); + } + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + } + } + } + } + } + break; + + case BTA_ACM_CONNECT_EVT: {// above evnt can come and handle for voice/media case + int contextType = p_acm->state_info.stream_type.type; + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s context=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), contextType); + LOG_INFO( + LOG_TAG, "%s: context=%d, converted=%d, Streaming context=%d", + __PRETTY_FUNCTION__, contextType, btif_acm_bap_to_acm_context(contextType), peer_.GetStreamContextType()); + if (btif_acm_bap_to_acm_context(contextType) != peer_.GetStreamContextType()) { + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music Rx, update state", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING){ + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for Music Rx, ignore", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music Tx, update state", __func__); + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING){ + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for Music Tx, ignore", __func__); + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } + } + if (p_acm->state_info.stream_state == StreamState::CONNECTED) + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_MUSIC); + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: voice context connected, remain in started state", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Tx, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Rx, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for voice Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + } + } + } else { + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + BTIF_TRACE_DEBUG("%s: peer device is suspended, send MM any pending ACK", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + BTIF_TRACE_DEBUG("%s: report STOP to apps and move to Opened", __func__); + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, peer_.GetStreamContextType()); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + + } break; + + case BTIF_ACM_RECONFIG_REQ_EVT: { + BTIF_TRACE_DEBUG("%s: sending stop to BAP before reconfigure", __func__); + btif_a2dp_source_end_session(active_bda); + peer_.SetFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + StreamType type_1; + std::vector stop_streams; + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + if (current_active_profile_type != WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + stop_streams.push_back(type_1); + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_1); + } + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + StreamType type_2; + type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_2); + stop_streams.push_back(type_1); + } + if (!sUcastClientInterface) break; + sUcastClientInterface->Stop(peer_.PeerAddress(), stop_streams); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateReconfiguring); + } + break; + + case BTA_ACM_CONFIG_EVT: { + tBTIF_ACM* p_acm_data = (tBTIF_ACM*)p_data; + uint16_t contextType = p_acm_data->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare current_media_config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm_data->config_info.codec_config); + } else if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm_data->config_info.codec_config; + } + if (mandatory_codec_selected) { + BTIF_TRACE_DEBUG("%s: Mandatory codec selected, do not store config", __PRETTY_FUNCTION__); + } else { + BTIF_TRACE_DEBUG("%s: store configuration", __PRETTY_FUNCTION__); + } + //Cache the peer latency in WMCP case + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL && + p_acm_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config", __PRETTY_FUNCTION__); + current_voice_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_voice_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_VOICE); + } + //Handle BAP START if reconfig comes in mid of streaming + //peer_.SetStreamReconfigInfo(p_acm->acm_reconfig); + //TODO: local capabilities + //CodecConfig record = p_bta_data->acm_reconfig.codec_config; + //saving codec config as negotiated parameter as true + //btif_pacs_add_record(peer_.PeerAddress(), true, CodecDirection::CODEC_DIR_SRC, &record); + + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + + return true; +} + +void BtifAcmStateMachine::StateReconfiguring::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if running if not, move to aggressive mode + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } else { + BTIF_TRACE_DEBUG("%s: conn timer not running, push aggressive intervals", __func__); + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } +} + +void BtifAcmStateMachine::StateReconfiguring::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateReconfiguring::ProcessEvent(uint32_t event, void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + + case BTA_ACM_STOP_EVT: { + BTIF_TRACE_DEBUG("%s: STOPPING event from BAP, ignore", __func__); + } break; + + case BTA_ACM_RECONFIG_EVT: { + BTIF_TRACE_DEBUG("%s: received reconfiguring state from BAP, ignore", __func__); + } break; + + case BTA_ACM_CONFIG_EVT: { + uint16_t contextType = p_acm->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare current_media_config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm->config_info.codec_config); + } else if (p_acm->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm->config_info.codec_config; + } + //Cache the peer latency in WMCP case + if (peer_.GetRcfgProfileType() == WMCP) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config"); + current_voice_config = p_acm->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm); + } + } break; + + case BTA_ACM_CONNECT_EVT: { + uint8_t status = (uint8_t)p_acm->state_info.stream_state; + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s flags=%s status=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), peer_.FlagsToString().c_str(), + status); + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + if (p_acm->state_info.stream_type.type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: Reconfig complete, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: Report Call audio config to apps? move to opened when both Voice Tx and Rx done", __func__); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Tx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: Report Call audio config to apps? move to opened when both Voice Tx and Rx done", __func__); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Rx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } + } + } + break; + } + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + BTIF_TRACE_DEBUG("%s: peer device is suspended, send MM any pending ACK", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, peer_.GetStreamContextType()); + std::vector reconf_streams; + StreamReconfig reconf_info; + CodecQosConfig cfg; + reconf_info.stream_type.type = CONTENT_TYPE_MEDIA; + // TODO to change audio context based on use case ( media or gaming or Live audio) + if (peer_.GetRcfgProfileType() != WMCP) { + reconf_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_info.stream_type.direction = ASE_DIRECTION_SINK; + } else { + reconf_info.stream_type.audio_context = CONTENT_TYPE_LIVE; + reconf_info.stream_type.direction = ASE_DIRECTION_SRC; + } + reconf_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + cfg = peer_.get_peer_media_codec_qos_config(); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + peer_.SetFlags(BtifAcmPeer::kFlagPendingReconfigure); + if (!sUcastClientInterface) break; + sUcastClientInterface->Reconfigure(peer_.PeerAddress(), reconf_streams); + } + } break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in reconfiguring state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in reconfiguring state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in reconfiguring state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + if ((peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING)) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when Voice Tx+Rx and Media Rx/Tx disconnected/ing, move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in reconfiguring state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in reconfiguring state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in reconfiguring state", __func__); + } + } + } + } + } + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + return true; +} + +void BtifAcmStateMachine::StateClosing::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if running if not, move to aggressive mode + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + else { + BTIF_TRACE_DEBUG("%s: conn timer not running, push aggressive intervals", __func__); + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + +} + +void BtifAcmStateMachine::StateClosing::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateClosing::ProcessEvent(uint32_t event, void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + case BTIF_ACM_START_STREAM_REQ_EVT: + case BTA_ACM_STOP_EVT: + case BTIF_ACM_STOP_STREAM_REQ_EVT: + break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in closing state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in closing state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in closing state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received Music Tx or Rx disconnecting state from BAP, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: received voice Tx or Rx disconnecting state from BAP, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + } + } + } + break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + return false; + } + return true; +} + +void btif_acm_update_lc3q_params(int64_t* cs3, tBTIF_ACM* p_data) { + + /* ================================================================== + * CS3: Res |LC3Q-len| QTI | VMT | VML | ver/For_Als |LC3Q-support + * ================================================================== + * 0x00 |0B | 000A | FF | 0F | 01/03 | 10 + * ================================================================== + * CS4: Res + * ============================== + * 0x00,00,00,00,00,00,00,00 + * ============================== */ + + if (GetVendorMetaDataLc3QPref( + &p_data->config_info.codec_config)) { + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_1ST_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x10 << (LE_AUDIO_CS_3_1ST_BYTE_INDEX * 8)); + + uint8_t lc3q_ver = GetVendorMetaDataLc3QVer(&p_data->config_info.codec_config); + BTIF_TRACE_DEBUG("%s: lc3q_ver: %d", __func__, lc3q_ver); + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_2ND_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)lc3q_ver << (LE_AUDIO_CS_3_2ND_BYTE_INDEX * 8)); + + //*cs3 &= ~((int64_t)LE_AUDIO_MASK); + *cs3 |= (int64_t)LE_AUDIO_AVAILABLE_LICENSED; + + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_3RD_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x0F << (LE_AUDIO_CS_3_3RD_BYTE_INDEX * 8)); + + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_4TH_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0xFF << (LE_AUDIO_CS_3_4TH_BYTE_INDEX * 8)); + + *cs3 &= ~((int64_t)0xFFFF << (LE_AUDIO_CS_3_5TH_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x000A << (LE_AUDIO_CS_3_5TH_BYTE_INDEX * 8)); + + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_7TH_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x0B << (LE_AUDIO_CS_3_7TH_BYTE_INDEX * 8)); + + CodecConfig temp = unicast_codecs_capabilities.back(); + unicast_codecs_capabilities.pop_back(); + temp.codec_specific_3 = *cs3; + unicast_codecs_capabilities.push_back(temp); + } + BTIF_TRACE_DEBUG("%s: cs3: %" PRIi64, __func__, *cs3); + BTIF_TRACE_DEBUG("%s: cs3= 0x%" PRIx64, __func__, *cs3); +} + +static void btif_report_connection_state(const RawAddress& peer_address, + btacm_connection_state_t state, uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: peer_address=%s state=%d contextType=%d", __func__, + peer_address.ToString().c_str(), state, contextType); + if (btif_acm_initiator.Enabled()) { + do_in_jni_thread(FROM_HERE, + Bind(btif_acm_initiator.Callbacks()->connection_state_cb, + peer_address, state, contextType)); + } +} + +static void btif_report_audio_state(const RawAddress& peer_address, + btacm_audio_state_t state, uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: peer_address=%s state=%d contextType=%d", __func__, + peer_address.ToString().c_str(), state, contextType); + if (btif_acm_initiator.Enabled()) { + do_in_jni_thread(FROM_HERE, + Bind(btif_acm_initiator.Callbacks()->audio_state_cb, + peer_address, state, contextType)); + } +} + +void btif_acm_report_source_codec_state( + const RawAddress& peer_address, + const CodecConfig& codec_config, + const std::vector& codecs_local_capabilities, + const std::vector& + codecs_selectable_capabilities, int contextType) { + BTIF_TRACE_EVENT("%s: peer_address=%s contextType=%d", __func__, + peer_address.ToString().c_str(), contextType); + if (btif_acm_initiator.Enabled()) { + do_in_jni_thread(FROM_HERE, + Bind(btif_acm_initiator.Callbacks()->audio_config_cb, peer_address, + codec_config, codecs_local_capabilities, + codecs_selectable_capabilities, contextType)); + } +} + +static void btif_acm_handle_evt(uint16_t event, char* p_param) { + BtifAcmPeer* peer = nullptr; + BTIF_TRACE_DEBUG("Handle the ACM event = %d ", event); + switch (event) { + case BTIF_ACM_DISCONNECT_REQ_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTIF_ACM_CONN_DISC* p_acm = (tBTIF_ACM_CONN_DISC*)p_param; + peer = btif_acm_initiator.FindOrCreatePeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR( + "%s: Cannot find peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), + event); + return; + } else { + BTIF_TRACE_EVENT( + "%s: BTIF_ACM_DISCONNECT_REQ_EVT peer_address=%s" + ": contextType=%d", + __func__, p_acm->bd_addr.ToString().c_str(), + p_acm->contextType); + } + break; + } + case BTIF_ACM_START_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + case BTIF_ACM_STOP_STREAM_REQ_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTIF_ACM_CONN_DISC* p_acm = (tBTIF_ACM_CONN_DISC*)p_param; + peer = btif_acm_initiator.FindOrCreatePeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + case BTA_ACM_DISCONNECT_EVT: + case BTA_ACM_CONNECT_EVT: + case BTA_ACM_START_EVT: + case BTA_ACM_STOP_EVT: + case BTA_ACM_RECONFIG_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTA_ACM_STATE_INFO* p_acm = (tBTA_ACM_STATE_INFO*)p_param; + peer = btif_acm_initiator.FindOrCreatePeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + case BTA_ACM_CONFIG_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTA_ACM_CONFIG_INFO* p_acm = (tBTA_ACM_CONFIG_INFO*)p_param; + peer = btif_acm_initiator.FindPeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + case BTIF_ACM_RECONFIG_REQ_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTIF_ACM_RECONFIG* p_acm = (tBTIF_ACM_RECONFIG*)p_param; + peer = btif_acm_initiator.FindPeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO * p_acm = + (tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO *)p_param; + peer = btif_acm_initiator.FindPeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + + default : + BTIF_TRACE_DEBUG("UNHandled ACM event = %d ", event); + break; + } + peer->StateMachine().ProcessEvent(event, (void*)p_param); +} + +/** + * Process BTA CSIP events. The processing is done on the JNI + * thread. + */ +static void btif_acm_handle_bta_csip_event(uint16_t evt, char* p_param) { + BtifCsipEvent btif_csip_event(evt, p_param, sizeof(tBTA_CSIP_DATA)); + tBTA_CSIP_EVT event = btif_csip_event.Event(); + tBTA_CSIP_DATA* p_data = (tBTA_CSIP_DATA*)btif_csip_event.Data(); + BTIF_TRACE_DEBUG("%s: event=%s", __func__, btif_csip_event.ToString().c_str()); + + switch (event) { + case BTA_CSIP_LOCK_STATUS_CHANGED_EVT: { + const tBTA_LOCK_STATUS_CHANGED& lock_status_param = p_data->lock_status_param; + BTIF_TRACE_DEBUG("%s: app_id=%d, set_id=%d, status=%d ", __func__, + lock_status_param.app_id, lock_status_param.set_id, + lock_status_param.status); + + std::vector set_members =lock_status_param.addr; + + for (int j = 0; j < (int)set_members.size(); j++) { + BTIF_TRACE_DEBUG("%s: address =%s", __func__, set_members[j].ToString().c_str()); + } + + BTIF_TRACE_DEBUG("%s: Get current lock status: %d ", __func__, + btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id)); + if (btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id) == BtifAcmInitiator::kFlagStatusPendingLock) { + BTIF_TRACE_DEBUG("%s: lock was awaited for this set ", __func__); + } + + if (btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id) == BtifAcmInitiator::kFlagStatusPendingUnlock) { + BTIF_TRACE_DEBUG("%s: Unlock was awaited for this set ", __func__); + } + + BTIF_TRACE_DEBUG("%s: Get CSIP app id: %d ", __func__, + btif_acm_initiator.GetCsipAppId()); + if (btif_acm_initiator.GetCsipAppId() != lock_status_param.app_id) { + BTIF_TRACE_DEBUG("%s: app id mismatch ERROR!!! ", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + + switch (lock_status_param.status) { + case LOCK_RELEASED: + BTIF_TRACE_DEBUG("%s: unlocked attempt succeeded ", __func__); + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + break; + case LOCK_RELEASED_TIMEOUT: + BTIF_TRACE_DEBUG("%s: peer unlocked due to timeout ", __func__); + //in this case evaluate which device has sent TO and how to use it ? + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + break; + case ALL_LOCKS_ACQUIRED: + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusLocked); + btif_acm_handle_csip_status_locked(lock_status_param.addr, lock_status_param.set_id); + BTIF_TRACE_DEBUG("%s: All locks acquired ", __func__); + break; + case SOME_LOCKS_ACQUIRED_REASON_TIMEOUT: + //proceed to continue use case; + /*case SOME_LOCKS_ACQUIRED_REASON_DISC: + //proceed to continue use case; + BTIF_TRACE_DEBUG("%s: locked attempt succeeded with status = %d", __func__, lock_status_param.status); + BTIF_TRACE_DEBUG("%s: locked set member count = %d, setsize = %d", + __func__, (lock_status_param.addr).size(), setSize); + btif_acm_initiator.music_active_set_locked_dev_count_ += (lock_status_param.addr).size(); + btif_acm_initiator.locked_devices.insert(btif_acm_initiator.locked_devices.end(), + lock_status_param.addr.begin(), lock_status_param.addr.end()); + btif_acm_handle_csip_status_locked(lock_status_param.addr, lock_status_param.set_id); + if (btif_acm_initiator.music_active_set_locked_dev_count_ < setSize) { + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusSubsetLocked); + } else { + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusLocked); + } + break;*/ + case LOCK_DENIED: { + //proceed to discontinue use case; + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_cancel_group_procedure_timer(lock_status_param.set_id); + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + } break; + case INVALID_REQUEST_PARAMS: { + BTIF_TRACE_DEBUG("%s: invalid lock request ", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_cancel_group_procedure_timer(lock_status_param.set_id); + if (btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id) == BtifAcmInitiator::kFlagStatusPendingLock) + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + else + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusLocked); + } break; + default: + break; + } + } break; + case BTA_CSIP_SET_MEMBER_FOUND_EVT: { + const tBTA_SET_MEMBER_FOUND& set_member_param = p_data->set_member_param; + BTIF_TRACE_DEBUG("%s: set_id=%d, uuid=%d ", __func__, + set_member_param.set_id, + set_member_param.uuid); + } break; + + case BTA_CSIP_LOCK_AVAILABLE_EVT: { + const tBTA_LOCK_AVAILABLE& lock_available_param = p_data->lock_available_param; + BTIF_TRACE_DEBUG("%s: app_id=%d, set_id=%d ", __func__, + lock_available_param.app_id, lock_available_param.set_id); + } break; + } +} + +static void btif_acm_handle_csip_status_locked(std::vector addr, uint8_t setId) { + if (addr.empty()) { + BTIF_TRACE_ERROR("%s: vector size is empty", __func__); + return; + } + tA2DP_CTRL_CMD pending_cmd;// = A2DP_CTRL_CMD_START;//TODO: change to None + pending_cmd = btif_ahim_get_pending_command(); + std::vector::iterator itr; + int req = 0; + if (pending_cmd == A2DP_CTRL_CMD_START) { + req = BTIF_ACM_START_STREAM_REQ_EVT; + } else if (pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + req = BTIF_ACM_SUSPEND_STREAM_REQ_EVT; + } else if (pending_cmd == A2DP_CTRL_CMD_STOP) { + req = BTIF_ACM_STOP_STREAM_REQ_EVT; + } else { + BTIF_TRACE_EVENT("%s: No pending command, check if this list of peers belong to MusicActive streaming started group", __func__); +//if (btif_acm_initiator.IsMusicActiveGroupStarted() && (setId == btif_acm_initiator.MusicActiveCSetId())) +//req = BTIF_ACM_START_STREAM_REQ_EVT; + } + if (req) { + for (itr = addr.begin(); itr != addr.end(); itr++) { + btif_acm_initiator_dispatch_sm_event(*itr, static_cast(req)); + } + } +// BtifAcmPeer* peer_ = btif_acm_initiator.FindPeer(peer_address); + /*if ((peer_.IsPeerActiveForMusic() || !btif_acm_stream_started_ready())) { + // Immediately stop transmission of frames while suspend is pending + if (req == BTIF_ACM_STOP_STREAM_REQ_EVT) { + //btif_acm_on_stopped(nullptr); + } else if (req == BTIF_ACM_SUSPEND_STREAM_REQ_EVT) { + // ensure tx frames are immediately suspended + //btif_acm_source_set_tx_flush(true); + } + }*/ +} + +static void btif_acm_check_and_start_conn_Interval_timer(BtifAcmPeer* peer) { + + btif_acm_check_and_cancel_conn_Interval_timer(); + BTIF_TRACE_DEBUG("%s: ", __func__); + + alarm_set_on_mloop(btif_acm_initiator.AcmConnIntervalTimer(), + BtifAcmInitiator::kTimeoutConnIntervalMs, + btif_acm_initiator_conn_Interval_timer_timeout, + (void *)peer); +} + +static void btif_acm_check_and_cancel_conn_Interval_timer() { + + BTIF_TRACE_DEBUG("%s: ", __func__); + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + alarm_cancel(btif_acm_initiator.AcmConnIntervalTimer()); + } +} + + +static void btif_acm_initiator_conn_Interval_timer_timeout(void *data) { + + BTIF_TRACE_DEBUG("%s: ", __func__); + BtifAcmPeer *peer = (BtifAcmPeer *)data; + tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO p_data; + p_data.bd_addr = peer->PeerAddress(); + btif_transfer_context(btif_acm_handle_evt, BTA_ACM_CONN_UPDATE_TIMEOUT_EVT, + (char*)&p_data, + sizeof(tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO), NULL); +} + +static void btif_acm_check_and_start_group_procedure_timer(uint8_t setId) { + uint8_t *arg = NULL; + arg = (uint8_t *) osi_malloc(sizeof(uint8_t)); + BTIF_TRACE_DEBUG("%s: ", __func__); + btif_acm_check_and_cancel_group_procedure_timer(setId); + + *arg = setId; + alarm_set_on_mloop(btif_acm_initiator.AcmGroupProcedureTimer(), + BtifAcmInitiator::kTimeoutAcmGroupProcedureMs, + btif_acm_initiator_group_procedure_timer_timeout, + (void*) arg); + +} + +static void btif_acm_check_and_cancel_group_procedure_timer(uint8_t setId) { + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + BTIF_TRACE_ERROR("%s: acm group procedure already running for setId = %d, cancel", __func__, setId); + alarm_cancel(btif_acm_initiator.AcmGroupProcedureTimer()); + } +} + +static void btif_acm_initiator_group_procedure_timer_timeout(void *data) { + BTIF_TRACE_DEBUG("%s: ", __func__); + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + std::vector streaming_devices; + std::vector non_streaming_devices; + uint8_t *arg = (uint8_t*) data; + if (!arg) { + BTIF_TRACE_ERROR("%s: coordinate arg is null, return", __func__); + return; + } + uint8_t setId = *arg; + if (setId == INVALID_SET_ID) { + BTIF_TRACE_ERROR("%s: coordinate SetId is invalid, return", __func__); + if (arg) osi_free(arg); + return; + } + + cset_info = BTA_CsipGetCoordinatedSet(setId); + if (cset_info.size == 0) { + BTIF_TRACE_ERROR("%s: CSET info size is zero, return", __func__); + if (arg) osi_free(arg); + return; + } + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + //BTIF_TRACE_DEBUG("%s: address = %s", __func__, itr->ToString().c_str()); + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if ((peer == nullptr) || (peer != nullptr && !peer->IsStreaming())) { + non_streaming_devices.push_back(*itr); + } else { + streaming_devices.push_back(*itr); + } + } + } + + if (streaming_devices.size() > 0) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + BTIF_TRACE_DEBUG("%s: Get music active setid: %d", __func__, + btif_acm_initiator.MusicActiveCSetId()); + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + if (streaming_devices.size() < (cset_info.set_members).size()) { + // this case should continue with mono mode since all set members are not streaming + } + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + + if (non_streaming_devices.size() > 0) //do we need to unlock and then disconnect ?? + // le_Acl_disconnect (non_streaming_devices); + + if (arg) osi_free(arg); +} + +static void btif_acm_check_and_start_lock_release_timer(uint8_t setId) { + uint8_t *arg = NULL; + arg = (uint8_t *) osi_malloc(sizeof(uint8_t)); + + btif_acm_check_and_cancel_lock_release_timer(setId); + + *arg = setId; + alarm_set_on_mloop(btif_acm_initiator.MusicSetLockReleaseTimer(), + BtifAcmPeer::kTimeoutLockReleaseMs, + btif_acm_initiator_lock_release_timer_timeout, + (void*) arg); +} + +static void btif_acm_check_and_cancel_lock_release_timer(uint8_t setId) { + if (alarm_is_scheduled(btif_acm_initiator.MusicSetLockReleaseTimer())) { + BTIF_TRACE_ERROR("%s: lock release already running for setId = %d, cancel ", __func__, setId); + alarm_cancel(btif_acm_initiator.MusicSetLockReleaseTimer()); + } +} + +static void btif_acm_initiator_lock_release_timer_timeout(void *data) { + uint8_t *arg = (uint8_t*) data; + if (!arg) { + BTIF_TRACE_ERROR("%s: coordinate arg is null, return", __func__); + return; + } + uint8_t setId = *arg; + if (setId == INVALID_SET_ID) { + BTIF_TRACE_ERROR("%s: coordinate SetId is invalid, return", __func__); + if (arg) osi_free(arg); + return; + } + if ((btif_acm_initiator.GetGroupLockStatus(setId) != BtifAcmInitiator::kFlagStatusLocked) || + (btif_acm_initiator.GetGroupLockStatus(setId) != BtifAcmInitiator::kFlagStatusSubsetLocked)) { + BTIF_TRACE_ERROR("%s: SetId = %d Lock Status = %d returning", + __func__, setId, btif_acm_initiator.GetGroupLockStatus(setId)); + if (arg) osi_free(arg); + return; + } + if (!btif_acm_request_csip_unlock(setId)) { + BTIF_TRACE_ERROR("%s: error unlocking", __func__); + } + if (arg) osi_free(arg); +} + +static void bta_csip_callback(tBTA_CSIP_EVT event, tBTA_CSIP_DATA* p_data) { + BTIF_TRACE_DEBUG("%s: event: %d", __func__, event); + btif_transfer_context(btif_acm_handle_bta_csip_event, event, (char*)p_data, + sizeof(tBTA_CSIP_DATA), NULL); +} + +// Initializes the ACM interface for initiator mode +static bt_status_t init_acm_initiator( + btacm_initiator_callbacks_t* callbacks, int max_connected_acceptors, + const std::vector& codec_priorities) { + BTIF_TRACE_EVENT("%s", __func__); + return btif_acm_initiator.Init(callbacks, max_connected_acceptors, + codec_priorities); +} + +// Establishes the BAP connection with the remote acceptor device +static void connect_int(uint16_t uuid, char* p_param) { + tBTIF_ACM_CONN_DISC connection; + memset(&connection, 0, sizeof(tBTIF_ACM_CONN_DISC)); + memcpy(&connection, p_param, sizeof(connection)); + RawAddress peer_address = RawAddress::kEmpty; + BtifAcmPeer* peer = nullptr; + peer_address = connection.bd_addr; + if (uuid == ACM_UUID) { + peer = btif_acm_initiator.FindOrCreatePeer(peer_address); + } + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: peer is NULL", __func__); + return; + } + peer->SetContextType(connection.contextType); + peer->SetProfileType(connection.profileType); + BTIF_TRACE_DEBUG("%s: cummulative_profile_type %d", __func__, peer->GetProfileType()); + //peer->SetPrefContextType(preferredContext); + peer->StateMachine().ProcessEvent(BTIF_ACM_CONNECT_REQ_EVT, &connection); +} + +// Set the active peer for contexttype +static void set_acm_active_peer_int(const RawAddress& peer_address, + uint16_t contextType, uint16_t profileType, + std::promise peer_ready_promise) { + BTIF_TRACE_EVENT("%s: peer_address=%s", __func__, peer_address.ToString().c_str()); + if (peer_address.IsEmpty()) { + int setid = INVALID_SET_ID; + if (contextType == CONTEXT_TYPE_MUSIC) + setid = btif_acm_initiator.MusicActiveCSetId(); + else if (contextType == CONTEXT_TYPE_VOICE) + setid = btif_acm_initiator.VoiceActiveCSetId(); + + if (setid < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setid); + if (cset_info.size != 0) { + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer != nullptr && peer->IsStreaming() && + (contextType == peer->GetStreamContextType())) { + BTIF_TRACE_DEBUG("%s: peer is streaming %s ", __func__, peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + } + } else { + BTIF_TRACE_DEBUG("%s: set active for twm device ", __func__); + BtifAcmPeer* peer = nullptr; + if (contextType == CONTEXT_TYPE_MUSIC) + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.MusicActivePeer()); + else if (contextType == CONTEXT_TYPE_VOICE) + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (peer != nullptr && peer->IsStreaming() && + (contextType == peer->GetStreamContextType())) { + BTIF_TRACE_DEBUG("%s: peer is streaming %s ", __func__, peer->PeerAddress().ToString().c_str()); + if (contextType == CONTEXT_TYPE_MUSIC) + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.MusicActivePeer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + else if (contextType == CONTEXT_TYPE_VOICE) + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.VoiceActivePeer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + if (!btif_acm_initiator.SetAcmActivePeer(peer_address, contextType, profileType, + std::move(peer_ready_promise))) { + BTIF_TRACE_ERROR("%s: Error setting %s as active peer", __func__, + peer_address.ToString().c_str()); + } +} + +static bt_status_t connect_acm_initiator(const RawAddress& peer_address, + uint16_t contextType, uint16_t profileType, + uint16_t preferredContext) { + BTIF_TRACE_EVENT("%s: Peer %s contextType=%d profileType=%d preferredContext=%d", __func__, + peer_address.ToString().c_str(), contextType, profileType, preferredContext); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + + tBTIF_ACM_CONN_DISC conn; + conn.contextType = contextType; + conn.profileType = profileType; + conn.bd_addr = peer_address; + return btif_transfer_context(connect_int, ACM_UUID, (char*)&conn, + sizeof(tBTIF_ACM_CONN_DISC), NULL); +} + +static bt_status_t disconnect_acm_initiator(const RawAddress& peer_address, + uint16_t contextType) { + BTIF_TRACE_EVENT("%s: Peer %s contextType=%d", __func__, + peer_address.ToString().c_str(), contextType); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + + BtifAcmPeer* peer = btif_acm_initiator.FindOrCreatePeer(peer_address); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: peer is NULL", __func__); + return BT_STATUS_FAIL; + } + + tBTIF_ACM_CONN_DISC disc; + peer->ResetContextType(contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + peer->ResetProfileType(BAP|GCP|WMCP); + disc.profileType = BAP|GCP|WMCP; + } else if (contextType == CONTEXT_TYPE_VOICE) { + peer->ResetProfileType(BAP_CALL); + disc.profileType = BAP_CALL; + } else if (contextType == CONTEXT_TYPE_MUSIC_VOICE) { + peer->ResetProfileType(BAP|GCP|WMCP|BAP_CALL); + disc.profileType = BAP|GCP|WMCP|BAP_CALL; + } + BTIF_TRACE_DEBUG("%s: cummulative_profile_type %d", __func__, peer->GetProfileType()); + + disc.bd_addr = peer_address; + disc.contextType = contextType; + btif_transfer_context(btif_acm_handle_evt, BTIF_ACM_DISCONNECT_REQ_EVT, (char*)&disc, + sizeof(tBTIF_ACM_CONN_DISC), NULL); + return BT_STATUS_SUCCESS; +} + +static bt_status_t set_active_acm_initiator(const RawAddress& peer_address, + uint16_t profileType) { + uint16_t contextType = CONTEXT_TYPE_MUSIC; + if (profileType == BAP || profileType == GCP || profileType == WMCP) + contextType = CONTEXT_TYPE_MUSIC; + else if (profileType == BAP_CALL) + contextType = CONTEXT_TYPE_VOICE; + + BTIF_TRACE_EVENT("%s: Peer %s contextType=%d profileType=%d", __func__, + peer_address.ToString().c_str(), contextType, profileType); + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return BT_STATUS_NOT_READY; + } + + BtifAcmPeer* peer = nullptr; + if (contextType == CONTEXT_TYPE_MUSIC) { + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.MusicActivePeer()); + if ((peer != nullptr) && (peer->GetStreamContextType() == CONTEXT_TYPE_MUSIC) && + (peer->CheckFlags(BtifAcmPeer::kFlagPendingStart | BtifAcmPeer::kFlagPendingLocalSuspend | + BtifAcmPeer::kFlagPendingReconfigure))) { + LOG(WARNING) << __func__ << ": Active music device is pending start or suspend or reconfig"; + return BT_STATUS_NOT_READY; + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if ((peer != nullptr) && (peer->GetStreamContextType() == CONTEXT_TYPE_VOICE) && + (peer->CheckFlags(BtifAcmPeer::kFlagPendingStart | + BtifAcmPeer::kFlagPendingLocalSuspend))) { + LOG(WARNING) << __func__ << ": Active voice device is pending start or suspend"; + return BT_STATUS_NOT_READY; + } + } + std::promise peer_ready_promise; + std::future peer_ready_future = peer_ready_promise.get_future(); + set_acm_active_peer_int(peer_address, contextType, profileType, + std::move(peer_ready_promise)); + return BT_STATUS_SUCCESS; +} + +static bt_status_t start_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: Peer %s", __func__, peer_address.ToString().c_str()); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + int id = btif_acm_initiator.VoiceActiveCSetId(); + if (id < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending start request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p && p->IsConnected()) { + p->SetStreamContextType(contextType); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_START_STREAM_REQ_EVT); + } + } + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.VoiceActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending start to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_VOICE); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.VoiceActivePeer(), BTIF_ACM_START_STREAM_REQ_EVT); + } else { + BTIF_TRACE_DEBUG("%s: Unable to send start to twm device ", __func__); + } + } + return BT_STATUS_SUCCESS; +} + +static bt_status_t stop_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: Peer %s", __func__, peer_address.ToString().c_str()); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + + int id = btif_acm_initiator.VoiceActiveCSetId(); + if (id < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending stop request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p && p->IsConnected()) { + p->SetStreamContextType(contextType); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.VoiceActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending stop to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_VOICE); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.VoiceActivePeer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + } else { + BTIF_TRACE_DEBUG("%s: Unable to send stop to twm device ", __func__); + } + } + return BT_STATUS_SUCCESS; +} + +static bt_status_t codec_config_acm_initiator(const RawAddress& peer_address, + std::vector codec_preferences, + uint16_t contextType, uint16_t profileType) { + BTIF_TRACE_EVENT("%s", __func__); + + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return BT_STATUS_NOT_READY; + } + + if (peer_address.IsEmpty()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator, peer empty"; + return BT_STATUS_PARM_INVALID; + } + + std::promise peer_ready_promise; + std::future peer_ready_future = peer_ready_promise.get_future(); + bt_status_t status = BT_STATUS_SUCCESS; + if (status == BT_STATUS_SUCCESS) { + peer_ready_future.wait(); + } else { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator fails to config codec"; + } + return status; +} + +static bt_status_t change_codec_config_acm_initiator(const RawAddress& peer_address, + char* msg) { + BTIF_TRACE_DEBUG("%s: codec change string: %s", __func__, msg); + tBTIF_ACM_RECONFIG data; + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return BT_STATUS_NOT_READY; + } + + if (peer_address.IsEmpty()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator, peer empty"; + return BT_STATUS_PARM_INVALID; + } + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(peer_address); + if (peer == nullptr) + LOG(ERROR) << __func__ << ": BTIF ACM Initiator, peer is null"; + return BT_STATUS_FAIL; + + CodecQosConfig codec_qos_cfg; + memset(&codec_qos_cfg, 0, sizeof(codec_qos_cfg)); + if (!strcmp(msg, "GCP_TX") && peer->GetContextType() == CONTEXT_TYPE_MUSIC) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if (!strcmp(msg, "GCP_TX_RX") && peer->GetContextType() == CONTEXT_TYPE_MUSIC) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + codec_qos_cfg.qos_config.cig_config.cig_id++; + codec_qos_cfg.qos_config.ascs_configs[0].cig_id++; + peer->set_peer_media_qos_config(codec_qos_cfg.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_cfg); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if (!strcmp(msg, "MEDIA_TX")) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if (!strcmp(msg, "MEDIA_RX")) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_LIVE; //Live Audio Context + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + data.streams_info.stream_type.direction = ASE_DIRECTION_SRC; + SelectCodecQosConfig(peer_address, WMCP, MEDIA_CONTEXT, SRC, EB_CONFIG); + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } + print_codec_parameters(codec_qos_cfg.codec_config); + print_qos_parameters(codec_qos_cfg.qos_config); + btif_transfer_context(btif_acm_handle_evt, BTIF_ACM_RECONFIG_REQ_EVT, (char*)&data, + sizeof(tBTIF_ACM_RECONFIG), NULL); + return BT_STATUS_SUCCESS; +} + +bool reconfig_acm_initiator(const RawAddress& peer_address, int profileType) { + BTIF_TRACE_DEBUG("%s: profileType: %d", __func__, profileType); + tBTIF_ACM_RECONFIG data; + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return false; + } + + if (peer_address.IsEmpty()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator, peer empty"; + return false; + } + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(peer_address); + if (peer == nullptr) { + LOG(ERROR) << __func__ << ": BTIF ACM Initiator, peer is null"; + return false; + } + + CodecQosConfig codec_qos_cfg; + memset(&codec_qos_cfg, 0, sizeof(codec_qos_cfg)); + if ((profileType == GCP) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == GCP_TX_RX) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + codec_qos_cfg.qos_config.cig_config.cig_id++; + codec_qos_cfg.qos_config.ascs_configs[0].cig_id++; + peer->set_peer_media_qos_config(codec_qos_cfg.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_cfg); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == BAP) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == WMCP) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_LIVE; //Live Audio Context + data.streams_info.stream_type.direction = ASE_DIRECTION_SRC; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, WMCP, MEDIA_CONTEXT, SRC, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, WMCP, MEDIA_CONTEXT, SRC, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == BAP_CALL) && (peer->GetContextType() & CONTEXT_TYPE_VOICE)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + } + + peer->SetRcfgProfileType(profileType); + if (profileType != BAP_CALL) { + print_codec_parameters(codec_qos_cfg.codec_config); + print_qos_parameters(codec_qos_cfg.qos_config); + } + btif_transfer_context(btif_acm_handle_evt, BTIF_ACM_RECONFIG_REQ_EVT, (char*)&data, + sizeof(tBTIF_ACM_RECONFIG), NULL); + return true; +} + +static void cleanup_acm_initiator(void) { + BTIF_TRACE_EVENT("%s", __func__); + do_in_bta_thread(FROM_HERE, Bind(&BtifAcmInitiator::Cleanup, + base::Unretained(&btif_acm_initiator))); +} + +static const btacm_initiator_interface_t bt_acm_initiator_interface = { + sizeof(btacm_initiator_interface_t), + init_acm_initiator, + connect_acm_initiator, + disconnect_acm_initiator, + set_active_acm_initiator, + start_stream_acm_initiator, + stop_stream_acm_initiator, + codec_config_acm_initiator, + change_codec_config_acm_initiator, + cleanup_acm_initiator, +}; + +RawAddress btif_acm_initiator_music_active_peer(void) { + return btif_acm_initiator.MusicActivePeer(); +} + +RawAddress btif_acm_initiator_voice_active_peer(void) { + return btif_acm_initiator.VoiceActivePeer(); +} + +bool btif_acm_request_csip_lock(uint8_t setId) { + LOG_INFO(LOG_TAG, "%s", __func__); + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setId); + /*if (cset_info.p_srvc_uuid != ACM_UUID) { + return false; + }*/ + if (cset_info.size > cset_info.total_discovered) { + LOG_INFO(LOG_TAG, "%s not complete set discovered yet. size = %d discovered = %d", + __func__, cset_info.size, cset_info.total_discovered); + } + if (setId == cset_info.set_id) { + LOG_INFO(LOG_TAG, "%s correct set id", __func__); + } else { + return false; + } + + btif_acm_check_and_cancel_lock_release_timer(setId); + + //Aquire lock for entire group. + tBTA_SET_LOCK_PARAMS lock_params; //need to do memset ? + lock_params.app_id = btif_acm_initiator.GetCsipAppId(); + lock_params.set_id = cset_info.set_id; + lock_params.lock_value = LOCK_VALUE;//For lock + lock_params.members_addr = cset_info.set_members; + BTA_CsipSetLockValue (lock_params); + btif_acm_initiator.SetLockFlags(BtifAcmInitiator::kFlagStatusPendingLock); + btif_acm_initiator.SetOrUpdateGroupLockStatus(cset_info.set_id, + btif_acm_initiator.CheckLockFlags(BtifAcmInitiator::kFlagStatusPendingLock)); + return true; +} + +bool btif_acm_request_csip_unlock(uint8_t setId) { + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setId); + /*if (cset_info.p_srvc_uuid != Uuid::FromString("2B86")) { + return false; + }*/ + if (cset_info.size > cset_info.total_discovered) { + LOG_INFO(LOG_TAG, "%s not complete set discovered yet. size = %d discovered = %d", + __func__, cset_info.size, cset_info.total_discovered); + } + if (setId == cset_info.set_id) { + LOG_INFO(LOG_TAG, "%s correct app id", __func__); + } else { + return false; + } + //Aquire lock for entire group. + tBTA_SET_LOCK_PARAMS lock_params; //need to do memset ? + lock_params.app_id = btif_acm_initiator.GetCsipAppId(); + lock_params.set_id = cset_info.set_id; + lock_params.lock_value = UNLOCK_VALUE;//For Unlock + lock_params.members_addr = cset_info.set_members; + BTA_CsipSetLockValue (lock_params); + btif_acm_initiator.SetLockFlags(BtifAcmInitiator::kFlagStatusPendingUnlock); + btif_acm_initiator.SetOrUpdateGroupLockStatus(cset_info.set_id, + btif_acm_initiator.CheckLockFlags(BtifAcmInitiator::kFlagStatusPendingUnlock)); + return true; +} + +bool btif_acm_is_call_active(void) { + BtifAcmPeer* peer = nullptr; + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (peer != nullptr && (peer->IsStreaming() || peer->CheckFlags(BtifAcmPeer::kFlagPendingStart)) && + (peer->GetStreamContextType() == CONTEXT_TYPE_VOICE)) + return true; + + return false; +} + +void btif_acm_stream_start(void) { + LOG_INFO(LOG_TAG, "%s", __func__); + if (!btif_acm_initiator.Enabled()) + return; + bool ret = false; + if (false/*btif_acm_initiator.IsCsipRegistered() && (btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID)*/) { + ret = btif_acm_request_csip_lock(btif_acm_initiator.MusicActiveCSetId()); + if (ret == false) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + //call below in lock changed success CB + //should be dispatched to list of peers in active music group. + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + int id = btif_acm_initiator.MusicActiveCSetId(); + if (id < INVALID_SET_ID) { + bool send_neg_ack = true; + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending start request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p != nullptr && p->IsConnected()) { + send_neg_ack = false; + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_START_STREAM_REQ_EVT); + } + } + } + if (send_neg_ack) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending start to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator_music_active_peer()); + + if (p != nullptr && p->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: Already streaming ongoing", __func__); + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + return; + } + + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator_music_active_peer(), BTIF_ACM_START_STREAM_REQ_EVT); + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + } + } +} + +void btif_acm_stream_stop(void) { + LOG_INFO(LOG_TAG, "%s ", __func__); + bool ret = false; + if (false /*btif_acm_initiator.IsCsipRegistered() && (btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID)*/) { + ret = btif_acm_request_csip_lock(btif_acm_initiator.MusicActiveCSetId()); + if (ret == false) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending stop to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator_music_active_peer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator_music_active_peer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + } +} + +void btif_acm_stream_suspend(void) { + LOG_INFO(LOG_TAG, "%s", __func__); + if (!btif_acm_initiator.Enabled()) + return; + bool ret = false; + if (false /*btif_acm_initiator.IsCsipRegistered() && (btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID)*/) { + ret = btif_acm_request_csip_lock(btif_acm_initiator.MusicActiveCSetId()); + //call below in lock changed success CB. + //should be dispatched to list of peers in active music group. + if (ret == false) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + int id = btif_acm_initiator.MusicActiveCSetId(); + if (id < INVALID_SET_ID) { + bool send_neg_ack = true; + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending suspend request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p != nullptr && p->IsConnected()) { + send_neg_ack = false; + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_SUSPEND_STREAM_REQ_EVT); + } + } + } + if (send_neg_ack) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending suspend to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator_music_active_peer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator_music_active_peer(), BTIF_ACM_SUSPEND_STREAM_REQ_EVT); + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + } + } +} + +void btif_acm_disconnect(const RawAddress& peer_address, int context_type) { + LOG_INFO(LOG_TAG, "%s: peer %s", __func__, peer_address.ToString().c_str()); + disconnect_acm_initiator(peer_address, context_type); +} + +static void btif_acm_initiator_dispatch_sm_event(const RawAddress& peer_address, + btif_acm_sm_event_t event) { + BtifAcmEvent btif_acm_event(event, nullptr, 0); + BTIF_TRACE_EVENT("%s: peer_address=%s event=%s", __func__, + peer_address.ToString().c_str(), + btif_acm_event.ToString().c_str()); + + btif_transfer_context(btif_acm_handle_evt, event, (char *)&peer_address, + sizeof(RawAddress), NULL); +} + +bt_status_t btif_acm_initiator_execute_service(bool enable) { + BTIF_TRACE_EVENT("%s: service: %s", __func__, + (enable) ? "enable" : "disable"); + + if (enable) { + BTA_RegisterCsipApp(bta_csip_callback, base::Bind([](uint8_t status, uint8_t app_id) { + if (status != BTA_CSIP_SUCCESS) { + LOG(ERROR) << "Can't register CSIP module "; + return; + } + BTIF_TRACE_DEBUG("App ID: %d", app_id); + btif_acm_initiator.SetCsipAppId(app_id); + btif_acm_initiator.SetCsipRegistration(true);} )); + return BT_STATUS_SUCCESS; + } + + // Disable the service + //BTA_UnregisterCsipApp(); + return BT_STATUS_FAIL; +} + +// Get the ACM callback interface for ACM Initiator profile +const btacm_initiator_interface_t* btif_acm_initiator_get_interface(void) { + BTIF_TRACE_EVENT("%s", __func__); + return &bt_acm_initiator_interface; +} + +uint16_t btif_acm_get_active_device_latency() { + BtifAcmPeer* peer = btif_acm_initiator.FindMusicActivePeer(); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return 0; + } else { + return peer->GetPeerLatency(); + } +} + +static void SelectCodecQosConfig(const RawAddress& bd_addr, + int profile_type, int context_type, + int direction, int config_type) { + + BTIF_TRACE_DEBUG("%s: Peer %s , context type: %d, profile_type: %d," + " direction: %d config_type %d", __func__, + bd_addr.ToString().c_str(), context_type, + profile_type, direction, config_type); + + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + + uint8_t CigId = peer->CigId(); + uint8_t set_size = 0; + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(cset_info)); + cset_info = BTA_CsipGetCoordinatedSet(peer->SetId()); + BTIF_TRACE_DEBUG("%s: cset members size: %d", + __func__, (uint8_t)(cset_info.size)); + + if (cset_info.size == 0) { + BTIF_TRACE_WARNING("%s: this shud be case for stereo-HS, config_type %d", + __func__, config_type); + set_size = (config_type == STEREO_HS_CONFIG_1) ? 2 : 1; + } else { + set_size = cset_info.size; + } + + CodecConfig codec_config_; + CodecQosConfig codec_qos_config; + QosConfig qos_configs; + CISConfig cis_config; + std::vector vmcp_qos_config; + BTIF_TRACE_WARNING("%s: going for best config", __func__); + memset(&codec_config_, 0, sizeof(codec_config_)); + select_best_codec_config(bd_addr, context_type, profile_type, + &codec_config_, direction, config_type); + codec_qos_config.codec_config = codec_config_; + BTIF_TRACE_DEBUG("%s: sample rate : %d, frame_duration: %d, octets: %d, ", + __func__, static_cast(codec_config_.sample_rate), + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + + if (context_type == MEDIA_CONTEXT) { + if (profile_type != WMCP) { + vmcp_qos_config = get_qos_params_for_codec(profile_type, + MEDIA_LL_CONTEXT, + codec_config_.sample_rate, + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + } else { + vmcp_qos_config = get_qos_params_for_codec(profile_type, + MEDIA_HR_CONTEXT, + codec_config_.sample_rate, + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + } + } else if (context_type == VOICE_CONTEXT) { + vmcp_qos_config = get_qos_params_for_codec(profile_type, + VOICE_CONTEXT, + codec_config_.sample_rate, + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + } + BTIF_TRACE_DEBUG("%s: vmcp qos size: %d", + __func__, (uint8_t)vmcp_qos_config.size()); + + bool qhs_enable = false; + char qhs_value[PROPERTY_VALUE_MAX] = "false"; + property_get("persist.vendor.btstack.qhs_enable", qhs_value, "false"); + if (!strncmp("true", qhs_value, 4)) { + if (btm_acl_qhs_phy_supported(bd_addr, BT_TRANSPORT_LE)) { + qhs_enable = true; + } + } else { + qhs_enable = false; + } + + //TODO: fill cig id and cis count from + //Currently it is a single size vector + for (uint8_t j = 0; j < (uint8_t)vmcp_qos_config.size(); j++) { + if (vmcp_qos_config[j].mandatory == 0) { + uint32_t sdu_interval = vmcp_qos_config[j].sdu_int_micro_secs; + codec_qos_config.qos_config.cig_config = { + .cig_id = CigId, + .cis_count = set_size, + .packing = 0x01, // interleaved + .framing = vmcp_qos_config[j].framing, // unframed + .max_tport_latency_m_to_s = vmcp_qos_config[j].max_trans_lat, + .max_tport_latency_s_to_m = vmcp_qos_config[j].max_trans_lat, + .sdu_interval_m_to_s = { + static_cast(sdu_interval & 0xFF), + static_cast((sdu_interval >> 8)& 0xFF), + static_cast((sdu_interval >> 16)& 0xFF) + }, + .sdu_interval_s_to_m = { + static_cast(sdu_interval & 0xFF), + static_cast((sdu_interval >> 8)& 0xFF), + static_cast((sdu_interval >> 16)& 0xFF) + } + }; + BTIF_TRACE_DEBUG("%s: framing: %d, transport latency: %d" + " sdu_interval: %d", __func__, + vmcp_qos_config[j].framing, + vmcp_qos_config[j].max_trans_lat, + vmcp_qos_config[j].sdu_int_micro_secs); + BTIF_TRACE_DEBUG("%s: CIG: packing: %d, transport latency m to s: %d," + " transport latency s to m: %d", __func__, + codec_qos_config.qos_config.cig_config.packing, + codec_qos_config.qos_config.cig_config.max_tport_latency_m_to_s, + codec_qos_config.qos_config.cig_config.max_tport_latency_s_to_m); + BTIF_TRACE_DEBUG("%s: Filled CIG config ", __func__); + } + } + + for (uint8_t i = 0; i < set_size; i++) { + //Currently it is a single size vector + uint8_t check_memset = 0; + for (uint8_t j = 0; j < (uint8_t)vmcp_qos_config.size(); j++) { + if (vmcp_qos_config[j].mandatory == 0) { + memset(&cis_config, 0, sizeof(cis_config)); + if (!check_memset) + check_memset = 1; + cis_config.cis_id = i; + if (profile_type != WMCP) + cis_config.max_sdu_m_to_s = vmcp_qos_config[j].max_sdu_size; + else + cis_config.max_sdu_m_to_s = 0; + if ((context_type == VOICE_CONTEXT) || (profile_type == WMCP)) + cis_config.max_sdu_s_to_m = vmcp_qos_config[j].max_sdu_size; + else + cis_config.max_sdu_s_to_m = 0; + + BTIF_TRACE_DEBUG("%s: qhs_enable: %d", __func__, qhs_enable); + + if (qhs_enable) { + cis_config.phy_m_to_s = LE_QHS_PHY; + cis_config.phy_s_to_m = LE_QHS_PHY; + } else { + cis_config.phy_m_to_s = LE_2M_PHY;//2mbps + cis_config.phy_s_to_m = LE_2M_PHY; + } + cis_config.rtn_m_to_s = vmcp_qos_config[j].retrans_num; + cis_config.rtn_s_to_m = vmcp_qos_config[j].retrans_num; + } + } + if (!check_memset) + memset(&cis_config, 0, sizeof(cis_config)); + codec_qos_config.qos_config.cis_configs.push_back(cis_config); + BTIF_TRACE_DEBUG("%s: Filled CIS config for %d", __func__, i); + } + + for (uint8_t j = 0; j < (uint8_t)vmcp_qos_config.size(); j++) { + if (vmcp_qos_config[j].mandatory == 0) { + uint32_t presen_delay = vmcp_qos_config[j].presentation_delay; + ASCSConfig ascs_config_1 = { + .cig_id = CigId, + .cis_id = peer->CisId(), + .target_latency = 0x03,//Target higher reliability + .bi_directional = false, + .presentation_delay = {static_cast(presen_delay & 0xFF), + static_cast((presen_delay >> 8)& 0xFF), + static_cast((presen_delay >> 16)& 0xFF)} + }; + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config_1); + BTIF_TRACE_DEBUG("%s: presentation delay = %d", __func__, presen_delay); + BTIF_TRACE_DEBUG("%s: Filled ASCS config for %d", __func__, ascs_config_1.cis_id); + if (config_type == STEREO_HS_CONFIG_1) { + ASCSConfig ascs_config_2 = ascs_config_1; + ascs_config_2.cis_id = peer->CisId()+1; + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config_2); + BTIF_TRACE_DEBUG("%s: Filled ASCS config for %d", __func__, ascs_config_2.cis_id); + } + } + } + + if (profile_type == BAP) { + if (context_type == VOICE_CONTEXT) { + if (direction == SNK) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[0].bi_directional = true; + if (config_type == STEREO_HS_CONFIG_1) { + codec_qos_config.qos_config.ascs_configs[1].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[1].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[1].bi_directional = true; + } + peer->set_peer_voice_tx_codec_config(codec_config_); + peer->set_peer_voice_tx_qos_config(codec_qos_config.qos_config); + peer->set_peer_voice_tx_codec_qos_config(codec_qos_config); + } else if (direction == SRC) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[0].bi_directional = true; + if (config_type == STEREO_HS_CONFIG_1) { + codec_qos_config.qos_config.ascs_configs[1].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[1].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[1].bi_directional = true; + } + peer->set_peer_voice_rx_codec_config(codec_config_); + peer->set_peer_voice_rx_qos_config(codec_qos_config.qos_config); + peer->set_peer_voice_rx_codec_qos_config(codec_qos_config); + } + } else { + peer->set_peer_media_codec_config(codec_config_); + peer->set_peer_media_qos_config(codec_qos_config.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_config); + } + } else if (profile_type == GCP) { + if (context_type == VOICE_CONTEXT) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 1; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 1; + codec_qos_config.qos_config.ascs_configs[0].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[0].bi_directional = true; + peer->set_peer_voice_rx_codec_config(codec_config_); + peer->set_peer_voice_rx_qos_config(codec_qos_config.qos_config); + peer->set_peer_voice_rx_codec_qos_config(codec_qos_config); + } else { + peer->set_peer_media_codec_config(codec_config_); + peer->set_peer_media_qos_config(codec_qos_config.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_config); + } + } else if (profile_type == WMCP) { + if (context_type == MEDIA_CONTEXT) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 3; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 3; + if (config_type == STEREO_HS_CONFIG_1) + codec_qos_config.qos_config.ascs_configs[1].cig_id = CigId + 3; + peer->set_peer_media_codec_config(codec_config_); + peer->set_peer_media_qos_config(codec_qos_config.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_config); + } + } + //print_codec_parameters(codec_config_); + //print_qos_parameters(codec_qos_config.qos_config); +} + +static bool select_best_sample_rate(uint16_t samp_freq, CodecConfig *result_config) { + BTIF_TRACE_DEBUG("%s: samp_freq: %d", __func__, samp_freq); + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_48000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_44100)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_32000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_24000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_16000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_8000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + return true; + } + return false; +} + +static bool select_best_frame_dura(uint8_t frame_dura, + CodecConfig *result_config) { + BTIF_TRACE_DEBUG("%s: frame_duration: %d", __func__, frame_dura); + if (frame_dura & static_cast(CodecFrameDuration::FRAME_DUR_10)) { + BTIF_TRACE_DEBUG("%s: selecting 10ms as best frame duration", __func__); + UpdateFrameDuration(result_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + return true; + } + + if ((frame_dura & + static_cast(CodecFrameDuration::FRAME_DUR_7_5)) == 0) { + BTIF_TRACE_DEBUG("%s: selecting 7.5ms as best frame duration", __func__); + UpdateFrameDuration(result_config, + static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + return true; + } + return true; +} + +void select_best_codec_config(const RawAddress& bd_addr, + uint16_t context_type, + uint8_t profile_type, + CodecConfig *codec_config, + int dir, int config_type) { + + BTIF_TRACE_DEBUG("%s: select best codec config for context type: %d," + " profile type %d config_type %d", __func__, + context_type, profile_type, config_type); + + CodecConfig result_codec_config; + uint16_t vmcp_samp_freq = 0; + uint8_t vmcp_fram_dur = 0; + uint32_t vmcp_octets_per_frame = 0; + std::vector pac_record; + std::vector local_codec_config; + memset(&result_codec_config, 0, sizeof(result_codec_config)); + bool pac_found = false; + uint16_t audio_context_type = CONTENT_TYPE_UNSPECIFIED; + + bool is_lc3q_supported = false; + char lc3q_value[PROPERTY_VALUE_MAX] = "false"; + property_get("persist.vendor.service.bt.is_lc3q_supported", lc3q_value, "false"); + if (!strncmp("true", lc3q_value, 4)) { + is_lc3q_supported = true; + } else { + is_lc3q_supported = false; + } + BTIF_TRACE_IMP("%s: is_lc3q_supported: %d", __func__, is_lc3q_supported); + + if (context_type == MEDIA_CONTEXT) { + audio_context_type |= + (profile_type == WMCP) ? CONTENT_TYPE_LIVE : CONTENT_TYPE_MEDIA; + } else if (context_type == VOICE_CONTEXT) { + audio_context_type |= CONTENT_TYPE_CONVERSATIONAL; + } + BTIF_TRACE_IMP("%s: audio_context_type: %d", __func__, audio_context_type); + + pac_found = btif_bap_get_records(bd_addr, REC_TYPE_CAPABILITY, audio_context_type, + ((dir == SRC) ? CodecDirection::CODEC_DIR_SRC : CodecDirection::CODEC_DIR_SINK), + &pac_record); + + if (pac_found) { + BTIF_TRACE_DEBUG("%s: PAC record found, select best codec config", __func__); + uint16_t peer_samp_freq = 0; + uint8_t peer_channel_mode = 0; + uint8_t peer_fram_dur = 0; + uint16_t peer_min_octets_per_frame = 0; + uint16_t peer_max_octets_per_frame = 0; + uint8_t peer_max_sup_lc3_frames = 0; + uint16_t peer_preferred_context = 0; + bool peer_lc3q_pref = 0; + uint8_t peer_lc3q_ver = 0; + + //currently differentiating based on frequency later we will do on context type + for (auto it = pac_record.begin(); it != pac_record.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + //performing only for MUSIC context type based on 44.1KHz and 48KHz + BTIF_TRACE_DEBUG("%s: pac_record sample_rate: %d", __func__, it->sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + peer_samp_freq |= static_cast(it->sample_rate); + } + } + } + + local_codec_config = get_all_codec_configs(profile_type, context_type); + BTIF_TRACE_DEBUG("%s: vmcp codec size: %d", + __func__, (uint8_t)local_codec_config.size()); + for (auto it = local_codec_config.begin(); + it != local_codec_config.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + BTIF_TRACE_DEBUG("%s: local_codec_config sample_rate: %d", + __func__, it->sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } + } + } + + select_best_sample_rate(peer_samp_freq & vmcp_samp_freq, + &result_codec_config); + for (auto it = pac_record.begin(); it != pac_record.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + BTIF_TRACE_DEBUG("%s: pac_record sample_rate: %d," + " result_codec_config.sample_rate: %d", + __func__, it->sample_rate, + result_codec_config.sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + BTIF_TRACE_DEBUG("%s: selecting 48KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + BTIF_TRACE_DEBUG("%s: selecting 44.1KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + BTIF_TRACE_DEBUG("%s: selecting 32KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + BTIF_TRACE_DEBUG("%s: selecting 24KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + BTIF_TRACE_DEBUG("%s: selecting 16KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + BTIF_TRACE_DEBUG("%s: selecting 8KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } + } + } + + BTIF_TRACE_DEBUG("%s: PAC parameters, peer supported sample_freqncies=%d," + " channel_mode=%d, frame_dura=%d", __func__, + peer_samp_freq, peer_channel_mode, peer_fram_dur); + BTIF_TRACE_DEBUG("%s: PAC parameters, min_octets_per_frame=%d," + " max_octets_per_frame=%d, peer_max_sup_lc3_frames=%d", + __func__, peer_min_octets_per_frame, peer_max_octets_per_frame, + peer_max_sup_lc3_frames); + BTIF_TRACE_DEBUG("%s: PAC parameters, peer_preferred_context=%d", + __func__, peer_preferred_context); + BTIF_TRACE_DEBUG("%s: PAC parameters, peer_lc3q_pref=%d, peer_lc3q_ver=%d", + __func__, peer_lc3q_pref, peer_lc3q_ver); + + local_codec_config = get_all_codec_configs(profile_type, context_type); + BTIF_TRACE_DEBUG("%s: vmcp codec size: %d", + __func__, (uint8_t)local_codec_config.size()); + for (auto it = local_codec_config.begin(); + it != local_codec_config.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + BTIF_TRACE_DEBUG("%s: local_codec_config sample_rate: %d," + " result_codec_config.sample_rate: %d", + __func__, it->sample_rate, result_codec_config.sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } + } + } + + if (config_type == STEREO_HS_CONFIG_2) + vmcp_octets_per_frame = vmcp_octets_per_frame * 2; + + BTIF_TRACE_DEBUG("%s: VMCP parameters, sample_freq=%d," + " frame_duration=%d, octets=%d", __func__, + vmcp_samp_freq, vmcp_fram_dur, vmcp_octets_per_frame); + + result_codec_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + result_codec_config.codec_priority = CodecPriority::CODEC_PRIORITY_DEFAULT; + + if (config_type == STEREO_HS_CONFIG_2) { + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_STEREO; + } else { + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + } + + select_best_frame_dura((peer_fram_dur >> 1) & vmcp_fram_dur, &result_codec_config); + + if (vmcp_octets_per_frame < peer_min_octets_per_frame || + vmcp_octets_per_frame > peer_max_octets_per_frame) { + BTIF_TRACE_DEBUG("%s: octets per frame is out of bound: %d ", + __func__, vmcp_octets_per_frame); + UpdateOctsPerFrame(&result_codec_config, vmcp_octets_per_frame); + } else { + BTIF_TRACE_DEBUG("%s: octets per frame is in limit update 100 octets: %d ", + __func__, vmcp_octets_per_frame); + UpdateOctsPerFrame(&result_codec_config, vmcp_octets_per_frame);//TODO: make this as peer octets + } + + if (is_lc3q_supported) { + UpdateLc3QPreference(&result_codec_config, true); + } + UpdateCapaMaxSupLc3Frames(&result_codec_config, peer_max_sup_lc3_frames); + UpdateLc3BlocksPerSdu(&result_codec_config, 1); + UpdatePreferredAudioContext(&result_codec_config, audio_context_type); + *codec_config = result_codec_config; + } else { + BTIF_TRACE_DEBUG("%s: PAC record not found, select mandatory config", __func__); + mandatory_codec_selected = true; + std::vector codec_pref_config; + codec_pref_config = get_all_codec_configs(profile_type, context_type); + BTIF_TRACE_DEBUG("%s: vmcp codec size %d", __func__, (uint8_t)codec_pref_config.size()); + for (auto it = codec_pref_config.begin(); it != codec_pref_config.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + BTIF_TRACE_DEBUG("%s: selecting 48KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + BTIF_TRACE_DEBUG("%s: selecting 44.1KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + BTIF_TRACE_DEBUG("%s: selecting 32KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + BTIF_TRACE_DEBUG("%s: selecting 24KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + BTIF_TRACE_DEBUG("%s: selecting 16KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + BTIF_TRACE_DEBUG("%s: selecting 8KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } + } + } + + if (config_type == STEREO_HS_CONFIG_2) + vmcp_octets_per_frame = vmcp_octets_per_frame * 2; + + BTIF_TRACE_DEBUG("%s: VMCP parameters, frame_duration=%d, octets=%d", + __func__, vmcp_fram_dur, vmcp_octets_per_frame); + + result_codec_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + result_codec_config.codec_priority = CodecPriority::CODEC_PRIORITY_DEFAULT; + if (config_type == STEREO_HS_CONFIG_2) + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_STEREO; + else + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //select_best_sample_rate(peer_samp_freq & vmcp_samp_freq, &result_codec_config, context_type); + select_best_frame_dura(vmcp_fram_dur, &result_codec_config); + UpdateOctsPerFrame(&result_codec_config, vmcp_octets_per_frame); + if (is_lc3q_supported) { + UpdateLc3QPreference(&result_codec_config, true); + } + UpdateLc3BlocksPerSdu(&result_codec_config, 1);//currently making it for media case + UpdatePreferredAudioContext(&result_codec_config, audio_context_type); + BTIF_TRACE_DEBUG("%s: saved codec config", __func__); + *codec_config = result_codec_config; + } +} + +uint16_t btif_acm_get_sample_rate() { + if (current_active_config.sample_rate != + CodecSampleRate::CODEC_SAMPLE_RATE_NONE) { + BTIF_TRACE_DEBUG("[ACM]:%s: sample_rate = %d", + __func__, current_active_config.sample_rate); + return static_cast(current_active_config.sample_rate); + } else { + BTIF_TRACE_DEBUG("[ACM]:%s: default sample_rate = %d", + __func__, default_config.sample_rate); + return static_cast(default_config.sample_rate); + } +} + +uint8_t btif_acm_get_ch_mode() { + if (current_active_config.channel_mode != + CodecChannelMode::CODEC_CHANNEL_MODE_NONE) { + BTIF_TRACE_DEBUG("[ACM]:%s: channel mode = %d", + __func__, current_active_config.channel_mode); + return static_cast(current_active_config.channel_mode); + } else { + BTIF_TRACE_DEBUG("[ACM]:%s: channel mode = %d", + __func__, default_config.channel_mode); + return static_cast(default_config.channel_mode); + } +} + +uint32_t btif_acm_get_bitrate() { + //based on bitrate set (100(80kbps), 120 (96kbps), 155 (128kbps)) + uint32_t bitrate = 0; + uint16_t octets = static_cast(GetOctsPerFrame(¤t_active_config)); + BTIF_TRACE_DEBUG("[ACM]:%s: octets = %d",__func__, octets); + + switch (octets) { + case 26: + bitrate = 27800; + break; + + case 30: + if (btif_acm_get_sample_rate() == + (static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_8000))) { + bitrate = 24000; + } else { + bitrate = 32000; + } + break; + + case 40: + bitrate = 32000; + break; + + case 45: + bitrate = 48000; + break; + + case 60: + if (btif_acm_get_sample_rate() == + (static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_24000))) { + bitrate = 48000; + } else { + bitrate = 64000; + } + break; + + case 80: + bitrate = 64000; + break; + + case 98: + case 130: + bitrate = 95550; + break; + + case 75: + case 100: + bitrate = 80000; + break; + + case 90: + case 120: + bitrate = 96000; + break; + + case 117: + case 155: + bitrate = 124000; + break; + + default: + bitrate = 124000; + break; + } + BTIF_TRACE_DEBUG("[ACM]%s: bitrate = %d",__func__,bitrate); + return bitrate; +} + +uint32_t btif_acm_get_octets(uint32_t bit_rate) { + uint32_t octets = 0; + octets = GetOctsPerFrame(¤t_active_config); + BTIF_TRACE_DEBUG("[ACM]%s: octets = %d",__func__,octets); + return octets; +} + +uint16_t btif_acm_get_framelength() { + uint16_t frame_duration; + switch (GetFrameDuration(¤t_active_config)) { + case 0: + frame_duration = 7500; //7.5msec + break; + + case 1: + frame_duration = 10000; //10msec + break; + + default: + frame_duration = 10000; + } + BTIF_TRACE_DEBUG("[ACM]%s: frame duration = %d", + __func__,frame_duration); + return frame_duration; +} + +uint16_t btif_acm_get_current_active_profile() { + return current_active_profile_type; +} +uint8_t btif_acm_get_ch_count() {//update channel mode based on device connection + uint8_t ch_mode = 0; + if (current_active_config.channel_mode == + CodecChannelMode::CODEC_CHANNEL_MODE_STEREO) { + ch_mode = 0x02; + } else if (current_active_config.channel_mode == + CodecChannelMode::CODEC_CHANNEL_MODE_MONO) { + ch_mode = 0x01; + } + BTIF_TRACE_DEBUG("[ACM]%s: channel count = %d",__func__,ch_mode); + return ch_mode; +} + +bool btif_acm_is_codec_type_lc3q() { + BTIF_TRACE_DEBUG("[ACM]%s",__func__); + return GetVendorMetaDataLc3QPref(¤t_active_config); +} + +uint8_t btif_acm_lc3q_ver() { + BTIF_TRACE_DEBUG("[ACM]%s",__func__); + return GetVendorMetaDataLc3QVer(¤t_active_config); +} + +uint16_t btif_acm_bap_to_acm_context(uint16_t bap_context) { + switch (bap_context) { + case CONTENT_TYPE_MEDIA: + case CONTENT_TYPE_LIVE: + return CONTEXT_TYPE_MUSIC; + + case CONTENT_TYPE_CONVERSATIONAL: + return CONTEXT_TYPE_VOICE; + + default: + BTIF_TRACE_DEBUG("%s: Unknown bap context",__func__); + return CONTEXT_TYPE_UNKNOWN; + } +} + +static void btif_debug_acm_peer_dump(int fd, const BtifAcmPeer& peer) { + std::string state_str; + int state = peer.StateMachine().StateId(); + switch (state) { + case BtifAcmStateMachine::kStateIdle: + state_str = "Idle"; + break; + + case BtifAcmStateMachine::kStateOpening: + state_str = "Opening"; + break; + + case BtifAcmStateMachine::kStateOpened: + state_str = "Opened"; + break; + + case BtifAcmStateMachine::kStateStarted: + state_str = "Started"; + break; + + case BtifAcmStateMachine::kStateReconfiguring: + state_str = "Reconfiguring"; + break; + + case BtifAcmStateMachine::kStateClosing: + state_str = "Closing"; + break; + + default: + state_str = "Unknown(" + std::to_string(state) + ")"; + break; + } + + dprintf(fd, " Peer: %s\n", peer.PeerAddress().ToString().c_str()); + dprintf(fd, " Connected: %s\n", peer.IsConnected() ? "true" : "false"); + dprintf(fd, " Streaming: %s\n", peer.IsStreaming() ? "true" : "false"); + dprintf(fd, " State Machine: %s\n", state_str.c_str()); + dprintf(fd, " Flags: %s\n", peer.FlagsToString().c_str()); + +} + +bool compare_codec_config_(CodecConfig &first, CodecConfig &second) { + if (first.codec_type != second.codec_type) { + BTIF_TRACE_DEBUG("[ACM] Codec type mismatch %s",__func__); + return true; + } else if (first.sample_rate != second.sample_rate) { + BTIF_TRACE_DEBUG("[ACM] Sample rate mismatch %s",__func__); + return true; + } else if (first.bits_per_sample != second.bits_per_sample) { + BTIF_TRACE_DEBUG("[ACM] Bits per sample mismatch %s",__func__); + return true; + } else if (first.channel_mode != second.channel_mode) { + BTIF_TRACE_DEBUG("[ACM] Channel mode mismatch %s",__func__); + return true; + } else { + uint8_t frame_first = GetFrameDuration(&first); + uint8_t frame_second = GetFrameDuration(&second); + if (frame_first != frame_second) { + BTIF_TRACE_DEBUG("[ACM] frame duration mismatch %s",__func__); + return true; + } + uint8_t lc3blockspersdu_first = GetLc3BlocksPerSdu(&first); + uint8_t lc3blockspersdu_second = GetLc3BlocksPerSdu(&second); + if (lc3blockspersdu_first != lc3blockspersdu_second) { + BTIF_TRACE_DEBUG("[ACM] LC3blocks per SDU mismatch %s",__func__); + return true; + } + uint16_t octets_first = GetOctsPerFrame(&first); + uint16_t octets_second = GetOctsPerFrame(&second); + if (octets_first != octets_second) { + BTIF_TRACE_DEBUG("[ACM] LC3 octets mismatch %s",__func__); + return true; + } + return false; + } +} + +void print_codec_parameters(CodecConfig config) { + uint8_t frame = GetFrameDuration(&config); + uint8_t lc3blockspersdu = GetLc3BlocksPerSdu(&config); + uint16_t octets = GetOctsPerFrame(&config); + bool vendormetadatalc3qpref = GetCapaVendorMetaDataLc3QPref(&config); + uint8_t vendormetadatalc3qver = GetCapaVendorMetaDataLc3QVer(&config); + LOG_DEBUG( + LOG_TAG, + "codec_type=%d codec_priority=%d " + "sample_rate=0x%x bits_per_sample=0x%x " + "channel_mode=0x%x", + config.codec_type, config.codec_priority, + config.sample_rate, config.bits_per_sample, + config.channel_mode); + LOG_DEBUG( + LOG_TAG, + "frame_duration=%d, lc3_blocks_per_SDU=%d," + " octets_per_frame=%d, vendormetadatalc3qpref=%d," + " vendormetadatalc3qver=%d ", + frame, lc3blockspersdu, octets, + vendormetadatalc3qpref, vendormetadatalc3qver); +} + +void print_qos_parameters(QosConfig qos) { + LOG_DEBUG( + LOG_TAG, + "CIG --> cig_id=%d cis_count=%d " + "packing=%d framing=%d " + "max_tport_latency_m_to_s=%d " + "max_tport_latency_s_to_m=%d " + "sdu_interval_m_to_s[0] = %x " + "sdu_interval_m_to_s[1] = %x " + "sdu_interval_m_to_s[2] = %x ", + qos.cig_config.cig_id, qos.cig_config.cis_count, + qos.cig_config.packing, qos.cig_config.framing, + qos.cig_config.max_tport_latency_m_to_s, + qos.cig_config.max_tport_latency_s_to_m, + qos.cig_config.sdu_interval_m_to_s[0], + qos.cig_config.sdu_interval_m_to_s[1], + qos.cig_config.sdu_interval_m_to_s[2]); + for (auto it = qos.cis_configs.begin(); it != qos.cis_configs.end(); ++it) { + LOG_DEBUG( + LOG_TAG, + "CIS --> cis_id = %d max_sdu_m_to_s = %d " + "max_sdu_s_to_m=%d " + "phy_m_to_s = %d " + "phy_s_to_m = %d " + "rtn_m_to_s = %d " + "rtn_s_to_m = %d", + it->cis_id, it->max_sdu_m_to_s, + it->max_sdu_s_to_m, + it->phy_m_to_s, it->phy_s_to_m, + it->rtn_m_to_s, it->rtn_s_to_m); + } + for (auto it = qos.ascs_configs.begin(); it != qos.ascs_configs.end(); ++it) { + LOG_DEBUG( + LOG_TAG, + "ASCS --> cig_id = %d cis_id = %d " + "target_latency=%d " + "bi_directional = %d " + "presentation_delay[0] = %x " + "presentation_delay[1] = %x " + "presentation_delay[2] = %x ", + it->cig_id, + it->cis_id, + it->target_latency, + it->bi_directional, + it->presentation_delay[0], + it->presentation_delay[1], + it->presentation_delay[2]); + } +} + +static void btif_debug_acm_initiator_dump(int fd) { + bool enabled = btif_acm_initiator.Enabled(); + + dprintf(fd, "\nA2DP Source State: %s\n", (enabled) ? "Enabled" : "Disabled"); + if (!enabled) return; + //dprintf(fd, " Active peer: %s\n", + // btif_acm_initiator.ActivePeer().ToString().c_str()); + for (auto it : btif_acm_initiator.Peers()) { + const BtifAcmPeer* peer = it.second; + btif_debug_acm_peer_dump(fd, *peer); + } +} + +void btif_debug_acm_dump(int fd) { + btif_debug_acm_initiator_dump(fd); +} diff --git a/le_audio/system/bt/btif/src/btif_acm_source.cc b/le_audio/system/bt/btif/src/btif_acm_source.cc new file mode 100644 index 0000000000000000000000000000000000000000..f9b99bf41966aa92d9e4ce5ae2c9d583ab759745 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_acm_source.cc @@ -0,0 +1,242 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include +#include "bt_trace.h" +#include "btif_acm_source.h" +#include "btif_ahim.h" +#include "btif_acm.h" +#include "osi/include/thread.h" + +extern thread_t* get_worker_thread(); + +#if AHIM_ENABLED + + +void btif_acm_process_request(tA2DP_CTRL_CMD cmd) +{ + tA2DP_CTRL_ACK status = A2DP_CTRL_ACK_FAILURE; + // update pending command + btif_ahim_update_pending_command(cmd, AUDIO_GROUP_MGR); + + BTIF_TRACE_IMP("%s: cmd %u", __func__, cmd); + + switch (cmd) { + case A2DP_CTRL_CMD_START: + { + if (btif_acm_is_call_active()) { + BTIF_TRACE_IMP("%s: call active, return incall_failure", __func__); + status = A2DP_CTRL_ACK_INCALL_FAILURE; + } else { + // ACM is in right state + status = A2DP_CTRL_ACK_PENDING; + btif_acm_stream_start(); + } + btif_ahim_ack_stream_started(status, AUDIO_GROUP_MGR); + break; + } + + case A2DP_CTRL_CMD_SUSPEND: + { + if (btif_acm_is_call_active()) { + BTIF_TRACE_IMP("%s: call active, return success", __func__); + status = A2DP_CTRL_ACK_SUCCESS; + } else { + status = A2DP_CTRL_ACK_PENDING; + btif_acm_stream_suspend(); + } + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + break; + } + + case A2DP_CTRL_CMD_STOP: + { + status = A2DP_CTRL_ACK_SUCCESS; + if (btif_acm_is_call_active()) { + BTIF_TRACE_IMP("%s: call active, return success", __func__); + } else { + btif_acm_stream_stop(); + } + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + break; + } + default: + APPL_TRACE_ERROR("%s: unsupported command %d", __func__, cmd); + break; + } +} + + +void btif_acm_handle_event(uint16_t event, char* p_param) +{ + + switch(event) { + case BTIF_ACM_PROCESS_HIDL_REQ_EVT: + btif_acm_process_request((tA2DP_CTRL_CMD ) *p_param); + break; + default: + BTIF_TRACE_IMP("%s: unhandled event", __func__); + break; + } +} + +void process_hidl_req_acm(tA2DP_CTRL_CMD cmd) +{ + btif_transfer_context(btif_acm_handle_event, BTIF_ACM_PROCESS_HIDL_REQ_EVT, (char*)&cmd, sizeof(cmd), NULL); +} + +static btif_ahim_client_callbacks_t sAhimAcmCallbacks = { + 1, // mode + process_hidl_req_acm, + btif_acm_get_sample_rate, + btif_acm_get_ch_mode, + btif_acm_get_bitrate, + btif_acm_get_octets, + btif_acm_get_framelength, + btif_acm_get_ch_count, + nullptr, + btif_acm_get_current_active_profile, + btif_acm_is_codec_type_lc3q, + btif_acm_lc3q_ver +}; + +void btif_register_cb() +{ + reg_cb_with_ahim(AUDIO_GROUP_MGR, &sAhimAcmCallbacks); +} + +bt_status_t btif_acm_source_setup_codec() { + APPL_TRACE_EVENT("%s", __func__); + + bt_status_t status = BT_STATUS_FAIL; + + + APPL_TRACE_EVENT("%s ## setup_codec ##", __func__); + btif_ahim_setup_codec(AUDIO_GROUP_MGR); + + // TODO: check the status + return status; +} + +bool btif_acm_source_start_session(const RawAddress& peer_address) { + bt_status_t status = BT_STATUS_FAIL; + APPL_TRACE_DEBUG("%s: starting session for BD addr %s",__func__, + peer_address.ToString().c_str()); + + // initialize hal. + btif_ahim_init_hal(get_worker_thread(), AUDIO_GROUP_MGR); + + status = btif_acm_source_setup_codec(); + + btif_ahim_start_session(); + + return true; +} + +bool btif_acm_source_end_session(const RawAddress& peer_address) { + APPL_TRACE_DEBUG("%s: starting session for BD addr %s",__func__, + peer_address.ToString().c_str()); + + btif_ahim_end_session(); + + return true; +} + +bool btif_acm_source_restart_session(const RawAddress& old_peer_address, + const RawAddress& new_peer_address) { + bool is_streaming = btif_ahim_is_streaming(); + SessionType session_type = btif_ahim_get_session_type(); + + APPL_TRACE_IMP("%s: old_peer_address=%s, new_peer_address=%s, is_streaming=%d ", + __func__, old_peer_address.ToString().c_str(), + new_peer_address.ToString().c_str(), is_streaming); + + // TODO: do we need to check for new empty address + //CHECK(!new_peer_address.IsEmpty()); + + // If the old active peer was valid or if session is not + // unknown, end the old session. + if (!old_peer_address.IsEmpty() || + session_type != SessionType::UNKNOWN) { + btif_acm_source_end_session(old_peer_address); + } + + btif_acm_source_start_session(new_peer_address); + + return true; +} + +bool btif_acm_update_sink_latency_change(uint16_t sink_latency) { + APPL_TRACE_DEBUG("%s: update_sink_latency %d for active session ",__func__, + sink_latency); + + btif_ahim_set_remote_delay(sink_latency); + + return true; +} + +void btif_acm_source_command_ack(tA2DP_CTRL_CMD cmd, tA2DP_CTRL_ACK status) { + switch (cmd) { + case A2DP_CTRL_CMD_START: + btif_ahim_ack_stream_started(status, AUDIO_GROUP_MGR); + break; + case A2DP_CTRL_CMD_SUSPEND: + case A2DP_CTRL_CMD_STOP: + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + break; + default: + break; + } +} + +void btif_acm_source_on_stopped(tA2DP_CTRL_ACK status) { + APPL_TRACE_EVENT("%s: status %u", __func__, status); + + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + + btif_ahim_reset_pending_command(AUDIO_GROUP_MGR); +} + +void btif_acm_source_on_suspended(tA2DP_CTRL_ACK status) { + APPL_TRACE_EVENT("%s: status %u", __func__, status); + + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + + btif_ahim_reset_pending_command(AUDIO_GROUP_MGR); +} + +bool btif_acm_on_started(tA2DP_CTRL_ACK status) { + APPL_TRACE_EVENT("%s: status %u", __func__, status); + bool retval = false; + + if(0/* TODO: check if call is in progress*/) { + APPL_TRACE_WARNING("%s: call in progress, sending failure", __func__); + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_INCALL_FAILURE, AUDIO_GROUP_MGR); + } + else { + btif_ahim_ack_stream_started(status, AUDIO_GROUP_MGR); + retval = true; + } + + btif_ahim_reset_pending_command(AUDIO_GROUP_MGR); + return retval; +} + + +#endif // AHIM_ENABLED diff --git a/le_audio/system/bt/btif/src/btif_apm.cc b/le_audio/system/bt/btif/src/btif_apm.cc new file mode 100644 index 0000000000000000000000000000000000000000..02f5a6f5457cad638543faf223e7fbb680c2b63a --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_apm.cc @@ -0,0 +1,189 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#define LOG_TAG "btif_apm" + +#include +#include + +#include +#include + +#include "bt_common.h" +#include "btif_common.h" +#include "btif_ahim.h" + +#define A2DP_PROFILE 0x0001 +#define BROADCAST_BREDR 0x0400 +#define BROADCAST_LE 0x0800 +#define ACTIVE_VOICE_PROFILE_HFP 0x0002 + +std::mutex apm_mutex; +btapm_initiator_callbacks_t* callbacks_; + +static bt_status_t init(btapm_initiator_callbacks_t* callbacks); +static void cleanup(); +static bt_status_t update_active_device(const RawAddress& bd_addr, uint16_t profile, uint16_t audio_type); +static bt_status_t set_content_control_id(uint16_t content_control_id, uint16_t audio_type); +static bool apm_enabled = false; + + +#define CHECK_BTAPM_INIT() \ + do { \ + if (!apm_enabled) { \ + BTIF_TRACE_WARNING("%s: BTAV not initialized", __func__); \ + return BT_STATUS_NOT_READY; \ + } \ + } while (0) + +typedef enum { + BTIF_APM_AUDIO_TYPE_VOICE = 0x0, + BTIF_APM_AUDIO_TYPE_MEDIA, + + BTIF_APM_AUDIO_TYPE_SIZE +} btif_av_state_t; + +typedef struct { + RawAddress peer_bda; + int profile; +} btif_apm_device_profile_combo_t; + +typedef struct { + RawAddress peer_bda; +} btif_apm_get_active_profile; + +int active_profile_info; + +static btif_apm_device_profile_combo_t active_device_profile[BTIF_APM_AUDIO_TYPE_SIZE]; +static uint16_t content_control_id[BTIF_APM_AUDIO_TYPE_SIZE]; + +static void btif_update_active_device(uint16_t audio_type, char* param); +void btif_get_active_device(btif_av_state_t audio_type, RawAddress* peer_bda); +static void btif_update_content_control(uint16_t audio_type, char* param); +uint16_t btif_get_content_control_id(btif_av_state_t audio_type); + +static void btif_update_active_device(uint16_t audio_type, char* param) { + btif_apm_device_profile_combo_t new_device_profile; + if(audio_type != BTIF_APM_AUDIO_TYPE_MEDIA) + return; + + memcpy(&new_device_profile, param, sizeof(new_device_profile)); + active_device_profile[audio_type].peer_bda = new_device_profile.peer_bda; + active_device_profile[audio_type].profile = new_device_profile.profile; + BTIF_TRACE_WARNING("%s() New Active Device: %s, Profile: %x\n", __func__, + active_device_profile[audio_type].peer_bda.ToString().c_str(), + active_device_profile[audio_type].profile); + if(active_device_profile[audio_type].profile == A2DP_PROFILE) { + btif_ahim_update_current_profile(A2DP); + } else if(active_device_profile[audio_type].profile == BROADCAST_LE) { + btif_ahim_update_current_profile(BROADCAST); + } else { + btif_ahim_update_current_profile(AUDIO_GROUP_MGR); + } +} + +void btif_get_active_device(btif_av_state_t audio_type, RawAddress* peer_bda) { + if(audio_type >= BTIF_APM_AUDIO_TYPE_SIZE) + return; + peer_bda = &active_device_profile[audio_type].peer_bda; +} + +static void btif_update_content_control(uint16_t audio_type, char* param) { + if(audio_type >= BTIF_APM_AUDIO_TYPE_SIZE) + return; + uint16_t cc_id = (uint16_t)(*param); + content_control_id[audio_type] = cc_id; + /*Update ACM here*/ +} + +uint16_t btif_get_content_control_id(btif_av_state_t audio_type) { + if(audio_type >= BTIF_APM_AUDIO_TYPE_SIZE) + return 0; + return content_control_id[audio_type]; +} + +static const bt_apm_interface_t bt_apm_interface = { + sizeof(bt_apm_interface_t), + init, + update_active_device, + set_content_control_id, + cleanup, +}; + +const bt_apm_interface_t* btif_apm_get_interface(void) { + BTIF_TRACE_EVENT("%s", __func__); + return &bt_apm_interface; +} + +static bt_status_t init(btapm_initiator_callbacks_t* callbacks) { + BTIF_TRACE_EVENT("%s", __func__); + callbacks_ = callbacks; + apm_enabled = true; + + return BT_STATUS_SUCCESS; +} + +static void cleanup() { + BTIF_TRACE_EVENT("%s", __func__); + apm_enabled = false; +} + +static bt_status_t update_active_device(const RawAddress& bd_addr, uint16_t profile, uint16_t audio_type) { + BTIF_TRACE_EVENT("%s", __func__); + CHECK_BTAPM_INIT(); + btif_apm_device_profile_combo_t new_device_profile; + new_device_profile.peer_bda = bd_addr; + new_device_profile.profile = profile; + + std::unique_lock guard(apm_mutex); + + return btif_transfer_context(btif_update_active_device, (uint8_t)audio_type, + (char *)&new_device_profile, sizeof(btif_apm_device_profile_combo_t), NULL); +} + +static bt_status_t set_content_control_id(uint16_t content_control_id, uint16_t audio_type) { + BTIF_TRACE_EVENT("%s", __func__); + CHECK_BTAPM_INIT(); + + std::unique_lock guard(apm_mutex); + + return btif_transfer_context(btif_update_content_control, + (uint8_t)audio_type, (char *)&content_control_id, sizeof(content_control_id), NULL); +} + +void call_active_profile_info(const RawAddress& bd_addr, uint16_t audio_type) { + if (apm_enabled == true) { + BTIF_TRACE_WARNING("%s", __func__); + active_profile_info = callbacks_->active_profile_cb(bd_addr, audio_type); + BTIF_TRACE_WARNING("%s: profile info is %d", __func__, active_profile_info); + } +} + +int get_active_profile(const RawAddress& bd_addr, uint16_t audio_type) { + if (apm_enabled == true) { + BTIF_TRACE_WARNING("%s: active profile is %d ", __func__, active_profile_info); + return active_profile_info; + } + else { + BTIF_TRACE_WARNING("%s: APM is not enabled, returning HFP as active profile %d ", + __func__, ACTIVE_VOICE_PROFILE_HFP); + return ACTIVE_VOICE_PROFILE_HFP; + } +} + diff --git a/le_audio/system/bt/btif/src/btif_ascs_client.cc b/le_audio/system/bt/btif/src/btif_ascs_client.cc new file mode 100644 index 0000000000000000000000000000000000000000..d5853e6eec2653e16f5ea2d786d7f8b1d710e1c2 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_ascs_client.cc @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_ascs_client_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include +#include +#include +#include "osi/include/thread.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::ascs::AscsClient; +using bluetooth::bap::ascs::GattState; +using bluetooth::bap::ascs::AscsClientCallbacks; +using bluetooth::bap::ascs::AscsClientInterface; +using bluetooth::bap::ascs::AseOpId; +using bluetooth::bap::ascs::AseOpStatus; +using bluetooth::bap::ascs::AseParams; +using bluetooth::bap::ascs::AseCodecConfigOp; +using bluetooth::bap::ascs::AseQosConfigOp; +using bluetooth::bap::ascs::AseEnableOp; +using bluetooth::bap::ascs::AseDisableOp; +using bluetooth::bap::ascs::AseStartReadyOp; +using bluetooth::bap::ascs::AseStopReadyOp; +using bluetooth::bap::ascs::AseReleaseOp; +using bluetooth::bap::ascs::AseUpdateMetadataOp; + +namespace { + +class AscsClientInterfaceImpl; +std::unique_ptr AscsClientInstance; + +class AscsClientInterfaceImpl + : public AscsClientInterface, + public AscsClientCallbacks { + ~AscsClientInterfaceImpl() = default; + + void Init(AscsClientCallbacks* callbacks) override { + DVLOG(2) << __func__; + this->callbacks = callbacks; + + do_in_bta_thread( + FROM_HERE, + Bind(&AscsClient::Init, this)); + } + + void OnAscsInitialized(int status, int client_id) override { + do_in_jni_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnAscsInitialized, + Unretained(callbacks), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + GattState state) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_jni_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnConnectionState, + Unretained(callbacks), address, state)); + } + + void OnAseOpFailed(const RawAddress& address, AseOpId ase_op_id, + std::vector status) override { + do_in_jni_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseOpFailed, + Unretained(callbacks), + address, ase_op_id, status)); + } + + void OnAseState(const RawAddress& address, AseParams ase) override { + do_in_jni_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseState, + Unretained(callbacks), address, ase)); + } + + void OnSearchComplete(int status, + const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + do_in_jni_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnSearchComplete, + Unretained(callbacks), + status, + address, + sink_ase_list, + src_ase_list)); + } + + void Connect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Connect, + Unretained(AscsClient::Get()), + client_id, address, false)); + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Disconnect, + Unretained(AscsClient::Get()), + client_id, address)); + } + + void StartDiscovery(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::StartDiscovery, + Unretained(AscsClient::Get()), + client_id, address)); + } + + void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::GetAseState, + Unretained(AscsClient::Get()), + client_id, address, ase_id)); + } + + void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::CodecConfig, + Unretained(AscsClient::Get()), + client_id, address, codec_configs)); + } + + void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::QosConfig, + Unretained(AscsClient::Get()), + client_id, address, qos_configs)); + } + + void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Enable, + Unretained(AscsClient::Get()), + client_id, address, enable_ops)); + } + + void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Disable, + Unretained(AscsClient::Get()), + client_id, address, disable_ops)); + } + + void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::StartReady, + Unretained(AscsClient::Get()), + client_id, address, start_ready_ops)); + } + + void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::StopReady, + Unretained(AscsClient::Get()), + client_id, address, stop_ready_ops)); + } + + void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Release, + Unretained(AscsClient::Get()), + client_id, address, release_ops)); + } + + void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::UpdateStream, + Unretained(AscsClient::Get()), + client_id, address, metadata_ops)); + } + + void Cleanup(uint16_t client_id) override { + DVLOG(2) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::CleanUp, client_id)); + } + + private: + AscsClientCallbacks* callbacks; +}; + +} // namespace + +AscsClientInterface* btif_ascs_client_get_interface() { + if (!AscsClientInstance) + AscsClientInstance.reset(new AscsClientInterfaceImpl()); + + return AscsClientInstance.get(); +} diff --git a/le_audio/system/bt/btif/src/btif_bap_broadcast.cc b/le_audio/system/bt/btif/src/btif_bap_broadcast.cc new file mode 100644 index 0000000000000000000000000000000000000000..0bd393ca1e34c2a7f961028f6f35fd909680e119 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_broadcast.cc @@ -0,0 +1,1822 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#define LOG_TAG "btif_bap_broadcast" + +#include "btif_bap_broadcast.h" + +#include +#include +#include +#include +#include +#include + +#include "bt_common.h" +#include "bt_utils.h" +#include "btif_storage.h" +#include "btif_a2dp.h" +#include "btif_hf.h" +#include "btif_a2dp_control.h" +#include "btif_util.h" +#include "btu.h" +#include "osi/include/allocator.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "btif/include/btif_a2dp_source.h" +#include "device/include/interop.h" +#include "device/include/controller.h" +#include "btif_bat.h" +#include "btif_av.h" +#include "hcimsgs.h" +#include "btif_config.h" +#include "audio_a2dp_hw/include/audio_a2dp_hw.h" +#include +#include +#include +#include "btm_ble_api.h" +#include "btm_ble_api_types.h" +#include "ble_advertiser.h" +#if (OFF_TARGET_TEST_ENABLED == FALSE) +#include "audio_hal_interface/a2dp_encoding.h" +#endif +#include "controller.h" +#if (OFF_TARGET_TEST_ENABLED == TRUE) +#include "log/log.h" +#include "service/a2dp_hal_sim/audio_a2dp_hal_stub.h" +#endif +#include "state_machine.h" + +#define BIG_COMPILE 1 + +#define BTIF_BAP_BA_NUM_CB 1 +#define kDefaultMaxBroadcastSupported 1 +#define BTIF_BAP_BA_NUM_BMS 1 + +#define INPUT_DATAPATH 0x01 +#define OUTPUT_DATAPATH 0x02 +#define BROADCAST_SPLIT_STEREO 2 +#define BROADCAST_MONO_JOINT 1 +#define BROADCAST_MODE_HR 0x1000 +#define BROADCAST_MODE_LL 0x2000 +/***************************************************************************** + * Local type definitions + *****************************************************************************/ + typedef enum { + BIG_TERMINATED = 0, + BIG_CREATING, + BIG_CREATED, + BIG_RECONFIG, + BIG_TERMINATING, + BIG_DISABLING, + } btif_big_state_t; + +std::vector broadcast_codecs_capabilities; +btav_a2dp_codec_index_t lc3_codec_id = (btav_a2dp_codec_index_t)9; +static const btav_a2dp_codec_config_t broadcast_local_capability = + {lc3_codec_id, BTAV_A2DP_CODEC_PRIORITY_DEFAULT, + (BTAV_A2DP_CODEC_SAMPLE_RATE_48000 | + BTAV_A2DP_CODEC_SAMPLE_RATE_24000 | + BTAV_A2DP_CODEC_SAMPLE_RATE_16000), + BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24, + ((btav_a2dp_codec_channel_mode_t)(BTAV_A2DP_CODEC_CHANNEL_MODE_MONO | + BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO | + BTBAP_CODEC_CHANNEL_MODE_JOINT_STEREO)), 0, 0, 0, 0}; + +static btav_a2dp_codec_config_t default_config; +static btav_a2dp_codec_config_t current_config; +static int mBisMultiplier = 0; +//static bool isSplitEnabled = false; +static bool notify_key_generated = false; +Octet16 encryption_key; +std::vector mBroadcastID(3,0); +struct keyCalculator { + Octet16 rand; +}; +uint8_t enc_keylength = 16; +char local_param[3]; +RawAddress mBapBADevice = RawAddress({0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xCE}); +std::mutex session_wait_; +std::condition_variable session_wait_cv_; +bool mSession_wait; +bool mEncryptionEnabled = true; +bool restart_session = false; +extern int btif_max_av_clients; +extern const btgatt_interface_t* btif_gatt_get_interface(); +extern int btif_av_get_latest_device_idx_to_start(); +extern thread_t* get_worker_thread(); +int total_bises = 0; +typedef enum { + iso_unknown = 0, + setup_iso = 1, + remove_iso = 2, +}tBAP_BA_ISO_CMD; + +typedef struct { + uint32_t sdu_int; + uint16_t max_sdu; + uint16_t max_transport_latency; + uint8_t rtn; + uint8_t phy; + uint8_t packing; + uint8_t framing; +} tBAP_BA_BIG_PARAMS; + +tBAP_BA_BIG_PARAMS mBigParams = {10000, 100, 10, 2, 2/*LE 2M*/, 1/*Interleaved*/, 0/*unframed*/}; +#define PATH_ID 1 +tBAP_BA_ISO_CMD pending_cmd = iso_unknown; +int current_handle = -1; +int current_iso_index = 0; + +int config_req_handle = -1; +/** + * Local functions + */ +static void btif_bap_ba_generate_enc_key_local(int length); +static void btif_bap_ba_create_big(int adv_id); +static void btif_bap_ba_terminate_big(int adv_id, int big_handle); +static bool btif_bap_ba_setup_iso_datapath(int big_handle); +static bool btif_bap_ba_remove_iso_datapath(int big_handle); +static void btif_bap_ba_process_iso_setup(uint8_t status, uint16_t bis_handle); +static void btif_bap_ba_update_big_params(); +static void btif_bap_ba_handle_event(uint32_t event, char* p_param); +static void init_local_capabilities(); +static void btif_report_broadcast_state(int adv_id, + btbap_broadcast_state_t state); +static void btif_report_broadcast_audio_state(int adv_id, + btbap_broadcast_audio_state_t state); +static void btif_report_audio_config(int adv_id, + btav_a2dp_codec_config_t codec_config); +static void btif_report_setup_big(int setup, int adv_id, int big_handle, int num_bises); +static void btif_report_broadcast_id(); +static void btif_bap_process_request(tA2DP_CTRL_CMD cmd); +static void btif_broadcast_process_hidl_event(tA2DP_CTRL_CMD cmd); +static uint16_t btif_bap_get_transport_latency(); +static void btif_bap_ba_copy_broadcast_id(); +static void btif_bap_ba_generate_broadcast_id(); +static void btif_bap_ba_signal_session_ready() { + std::unique_lock guard(session_wait_); + if(!mSession_wait) { + mSession_wait = true; + session_wait_cv_.notify_all(); + } else { + BTIF_TRACE_WARNING("%s: already signalled ",__func__); + } +} + +const char* dump_bap_ba_sm_event_name(btif_bap_broadcast_sm_event_t event) { + switch ((int)event) { + CASE_RETURN_STR(BTIF_BAP_BROADCAST_ENABLE_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_DISABLE_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_CLEANUP_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BISES_SETUP_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BISES_REMOVE_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BIG_SETUP_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BIG_REMOVED_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT) + CASE_RETURN_STR(BTIF_SM_ENTER_EVT) + CASE_RETURN_STR(BTIF_SM_EXIT_EVT) + default: + return "UNKNOWN_EVENT"; + } +} + +void btif_bap_broadcast_update_source_codec(void *p_data) { + btav_a2dp_codec_config_t * codec_req = (btav_a2dp_codec_config_t*)p_data; + if (codec_req->sample_rate != current_config.sample_rate || + codec_req->channel_mode != current_config.channel_mode || + codec_req->codec_specific_1 != current_config.codec_specific_1 || + codec_req->codec_specific_2 != current_config.codec_specific_2) { + restart_session = true; + } + + if (codec_req->codec_specific_4 > 0) { + if (current_config.codec_specific_4 == BROADCAST_MODE_HR) { + mBigParams.max_transport_latency = 60; + } else if (codec_req->codec_specific_4 == BROADCAST_MODE_LL) { + mBigParams.max_transport_latency = 20; + } + } + memcpy(¤t_config, codec_req, sizeof(btav_a2dp_codec_config_t)); + BTIF_TRACE_DEBUG("[BapBroadcast]%s:sample rate: %d",__func__, current_config.sample_rate); + BTIF_TRACE_DEBUG("[BapBroadcast]%s:channel mode: %d",__func__, current_config.channel_mode); + BTIF_TRACE_DEBUG("[BapBroadcast]%s:cs1: %d",__func__, current_config.codec_specific_1); + btif_bap_ba_update_big_params(); +} + +void reverseCode(uint8_t *array) { + uint8_t *p_array = array; + for (int i = 0; i < 8; i++) { + uint8_t temp = p_array[i]; + p_array[i] = p_array[15-i]; + p_array[15-i] = temp; + } +} + +bool isUnencrypted(uint8_t *array) { + uint8_t *p_array = array; + for (int i = 0; i < 16; i++) { + if (p_array[i] != 0x00) { + return false; + } + } + BTIF_TRACE_DEBUG("[BapBroadcast]: isUnencrypted is true"); + return true; +} +class BtifBapBroadcaster; + +class BtifBapBroadcastStateMachine : public bluetooth::common::StateMachine{ + public: + enum { + kStateIdle, // Broadcast idle + kStateConfigured, // Broadcast configured + kStateStreaming, // Broadcast streaming + }; + + class StateIdle : public State { + public: + StateIdle(BtifBapBroadcastStateMachine& sm) + : State(sm, kStateIdle), bms_(sm.Bms()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifBapBroadcaster& bms_; + }; + + class StateConfigured : public State { + public: + StateConfigured(BtifBapBroadcastStateMachine& sm) + : State(sm, kStateConfigured), bms_(sm.Bms()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifBapBroadcaster& bms_; + }; + + class StateStreaming : public State { + public: + StateStreaming(BtifBapBroadcastStateMachine& sm) + : State(sm, kStateStreaming), bms_(sm.Bms()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifBapBroadcaster& bms_; + }; + + BtifBapBroadcastStateMachine(BtifBapBroadcaster& bms) : bms_(bms) { + state_idle_ = new StateIdle(*this); + state_configured_ = new StateConfigured(*this); + state_streaming_ = new StateStreaming(*this); + + AddState(state_idle_); + AddState(state_configured_); + AddState(state_streaming_); + SetInitialState(state_idle_); + } + BtifBapBroadcaster& Bms() { return bms_; } + private: + BtifBapBroadcaster& bms_; + StateIdle* state_idle_; + StateConfigured* state_configured_; + StateStreaming* state_streaming_; +}; + +class BtifBapBroadcaster{ + public: + enum { + kFlagBIGPending = 0x1, + kFlagISOPending = 0x2, + kFlagISOError = 0x4, + KFlagISOSetup = 0x8, + }; + + BtifBapBroadcaster(int adv_handle, int big_handle) + :adv_handle_(adv_handle), + big_handle_(big_handle), + state_machine_(*this), + flags_(0), + big_state_(BIG_TERMINATED){} + + ~BtifBapBroadcaster(); + + bt_status_t Init(); + void Cleanup(); + + bool CanBeDeleted() {return ( + (state_machine_.StateId() == BtifBapBroadcastStateMachine::kStateIdle) && + (state_machine_.PreviousStateId() != BtifBapBroadcastStateMachine::kStateInvalid)); }; + + int AdvHandle() const { return adv_handle_; } + int BIGHandle() const { return big_handle_; } + void SetBIGHandle(int handle) { big_handle_ = handle; } + void SetAdvHandle(int handle) { adv_handle_ = handle; } + BtifBapBroadcastStateMachine& StateMachine() { return state_machine_; } + const BtifBapBroadcastStateMachine& StateMachine() const { return state_machine_; } + bool CheckFlags(uint8_t bitflags_mask) const { + return ((flags_ & bitflags_mask) != 0); + } + + void ClearFlag(uint8_t bitflags_mask) { flags_ &= ~bitflags_mask;} + + void ClearAllFlags() { flags_ = 0; } + /** + * Set only the flags as specified by the bitflags mask. + * + * @param bitflags_mask the bitflags to set + */ + void SetFlags(uint8_t bitflags_mask) { flags_ |= bitflags_mask; } + + void SetNumBises(uint8_t num_bises) {num_bises_ = num_bises; } + + uint8_t NumBises() const { return num_bises_; } + + void SetBIGState(btif_big_state_t state) { big_state_ = state; } + + btif_big_state_t BIGState() const { return big_state_; } + + /*void SetMandatoryCodecPreferred(bool preferred) { + mandatory_codec_preferred_ = preferred; + } + bool IsMandatoryCodecPreferred() const { return mandatory_codec_preferred_; }*/ + + std::vector GetBISHandles() const { return bis_handle_list_;} + void SetBISHandles(std::vector handle_list) { bis_handle_list_ = handle_list; } + + private: + int adv_handle_; + int big_handle_; // SEP type of peer device + uint8_t num_bises_; + BtifBapBroadcastStateMachine state_machine_; + uint8_t flags_; + btif_big_state_t big_state_; + //bool mandatory_codec_preferred_ = false; + std::vector bis_handle_list_; +}; +//BtifBapBroadcaster::BtifBapBroadcaster(int adv_handle, int big_handle) +// :adv_handle_(adv_handle), big_handle_(big_handle){} + +class BtifBapBroadcastSource{ + public: + // The PeerId is used as AppId for BTA_AvRegister() purpose + static constexpr uint8_t kPeerIdMin = 0; + static constexpr uint8_t kPeerIdMax = BTIF_BAP_BA_NUM_BMS; + public: + enum { + kFlagIdle = 0x1, + kFlagConfigured = 0x2, + kFlagStreaming = 0x4, + KFlagDisabling = 0x8, + }; + BtifBapBroadcastSource() + : callbacks_(nullptr), + enabled_(false), + offload_enabled_(false), + max_broadcast_(kDefaultMaxBroadcastSupported) {} + ~BtifBapBroadcastSource(); + + bt_status_t Init( + btbap_broadcast_callbacks_t* callbacks, int max_broadcast, + btav_a2dp_codec_config_t codec_config,int mode); + + bt_status_t EnableBroadcast(btav_a2dp_codec_config_t codec_config); + bt_status_t DisableBroadcast(int adv_handle); + void Cleanup(); + void CleanupIdleBms(); + btbap_broadcast_callbacks_t* Callbacks() { return callbacks_; } + void SetEnabled(bool state) { enabled_ = state; } + bool Enabled() const { return enabled_; } + bool BapBroadcastOffloadEnabled() const { return offload_enabled_; } + + BtifBapBroadcaster* FindBmsFromAdvHandle(uint8_t adv_handle); +// BtifBapBroadcaster* FindEmptyBms(); + BtifBapBroadcaster* FindBmsFromBIGHandle(uint8_t big_handle); + BtifBapBroadcaster* FindStreamingBms(); + BtifBapBroadcaster* FindConfiguredBms(); + BtifBapBroadcaster* CreateBMS(int adv_handle); + //void SetDefaultConfig(btav_a2dp_codec_config_t config) { default_config_ = config; } + btav_a2dp_codec_config_t GetDefaultConfig () const { return default_config_; } + //void SetCurrentConfig (btav_a2dp_codec_config_t config) { current_config_ = config; } + btav_a2dp_codec_config_t GetCurrentConfig() const { return current_config_; } + bt_status_t SetEncryption(int length); + bt_status_t SetBroadcastActive(bool setup, uint8_t adv_id); + bool BroadcastActive() const { return ((broadcast_state_ == kFlagConfigured) + ||(broadcast_state_ == kFlagStreaming)); } + void SetBroadcastState(uint8_t flag) { broadcast_state_ = flag; } + uint8_t GetBroadcastState() { return broadcast_state_; } + bt_status_t SetUserConfig(uint8_t adv_hdl, btav_a2dp_codec_config_t codec_config); + + const std::map& Bms() const { return bms_; } + + private: + void CleanupAllBms(); + + btbap_broadcast_callbacks_t* callbacks_; + bool enabled_; + bool offload_enabled_; + int max_broadcast_; + std::map bms_; + btav_a2dp_codec_config_t default_config_; + btav_a2dp_codec_config_t current_config_; + uint8_t broadcast_state_; +}; + +static BtifBapBroadcastSource btif_bap_bms; + +bt_status_t BtifBapBroadcaster::Init() { + state_machine_.Start(); + return BT_STATUS_SUCCESS; +} + +void BtifBapBroadcaster::Cleanup() { + state_machine_.Quit(); +} + +void BtifBapBroadcastStateMachine::StateIdle::OnEnter() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); + + bms_.ClearAllFlags(); + bms_.SetAdvHandle(-1); + bms_.SetBIGHandle(-1); + bms_.SetBIGState(BIG_TERMINATED); + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::kFlagIdle); + btif_bap_bms.SetEnabled(false); + btif_bap_bms.CleanupIdleBms(); +} + +void BtifBapBroadcastStateMachine::StateIdle::OnExit() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); +} + +bool BtifBapBroadcastStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + BTIF_TRACE_IMP("[BapBroadcast]:%s: event = %s",__func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch (event) { + case BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT: + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + //copy config + break; + default: + BTIF_TRACE_WARNING("%s: unhandled event=%s", __func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + return false; + } + return true; +} + +void BtifBapBroadcastStateMachine::StateConfigured::OnEnter() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); + + // Inform the application that we are entering connecting state + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::kFlagConfigured); + btif_bap_bms.SetEnabled(true); + if (bms_.BIGState() == BIG_TERMINATED) { +#if AHIM_ENABLED + btif_ahim_init_hal(get_worker_thread(), BROADCAST); + btif_ahim_start_session(); +#else + btif_a2dp_source_restart_session(RawAddress::kEmpty, mBapBADevice); +#endif + btif_bap_ba_signal_session_ready(); + btif_bap_ba_update_big_params(); + } else if (bms_.BIGState() == BIG_DISABLING) { + ProcessEvent(BTIF_BAP_BROADCAST_DISABLE_EVT, nullptr); + return; + } + bms_.SetBIGState(BIG_TERMINATED); + bms_.ClearAllFlags(); + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_CONFIGURED); +} + +void BtifBapBroadcastStateMachine::StateConfigured::OnExit() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); +} + +bool BtifBapBroadcastStateMachine::StateConfigured::ProcessEvent(uint32_t event, + void* p_data) { + BTIF_TRACE_IMP("[BapBroadcast]:%s: event = %s",__func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch (event) { + + case BTIF_BAP_BROADCAST_DISABLE_EVT: + BTIF_TRACE_DEBUG("[BapBroadcast]:BTIF_BAP_BROADCAST_DISABLE_EVT, moving to idle"); + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagBIGPending) || + bms_.CheckFlags(BtifBapBroadcaster::kFlagISOPending)) { +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + } + bms_.SetBIGState(BIG_DISABLING); + bms_.ClearFlag(BtifBapBroadcaster::kFlagISOPending); + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_IDLE); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateIdle); + break; + case BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT: + BTIF_TRACE_DEBUG("Not handled in configured state"); + break; + case BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT: + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagBIGPending) || + bms_.CheckFlags(BtifBapBroadcaster::kFlagISOPending)) { + BTIF_TRACE_DEBUG("[BapBroadcast]%s: BIG/ISO setup pending, dup req",__func__); +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_PENDING, BROADCAST); +#else + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_PENDING, BROADCAST); +#endif + break; + } + bms_.SetFlags(BtifBapBroadcaster::kFlagBIGPending); + bms_.SetNumBises(btif_bap_broadcast_get_ch_count()); + bms_.SetBIGState(BIG_CREATING); + btif_bap_ba_create_big(bms_.AdvHandle()); + break; + case BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT: + { + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagBIGPending)) + bms_.ClearFlag(BtifBapBroadcaster::kFlagBIGPending); + bms_.SetFlags(BtifBapBroadcaster::kFlagISOPending); + total_bises = bms_.NumBises(); + current_iso_index = 0; + current_handle = bms_.BIGHandle(); + btif_bap_ba_setup_iso_datapath(current_handle); + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT: + total_bises = bms_.NumBises(); + current_iso_index = 0; + current_handle = bms_.BIGHandle(); + btif_bap_ba_remove_iso_datapath(current_handle); + break; + case BTIF_BAP_BROADCAST_BISES_SETUP_EVT: + { + char *p_p = (char *) p_data; + p_p++; + uint8_t status = *p_p; + if (status != BT_STATUS_SUCCESS && + bms_.CheckFlags(BtifBapBroadcaster::kFlagISOPending)) { + BTIF_TRACE_ERROR("[BapBroadcast]%s: setup iso failed",__func__); + bms_.ClearAllFlags(); + bms_.SetFlags(BtifBapBroadcaster::kFlagISOError); +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + break; + } + bms_.ClearFlag(BtifBapBroadcaster::kFlagISOPending); + bms_.SetFlags(BtifBapBroadcaster::KFlagISOSetup); +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + btif_report_setup_big(1, bms_.AdvHandle(), bms_.BIGHandle(), bms_.NumBises()); + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_STREAMING); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateStreaming); + } + break; + case BTIF_BAP_BROADCAST_BISES_REMOVE_EVT: +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + break; + case BTIF_BAP_BROADCAST_BIG_SETUP_EVT: + break; + case BTIF_BAP_BROADCAST_BIG_REMOVED_EVT: + btif_report_setup_big(0, bms_.AdvHandle(), -1, 0); + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagISOError)) { + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateIdle); + btif_report_broadcast_state(bms_.AdvHandle(),BTBAP_BROADCAST_STATE_IDLE); + } + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + btif_bap_broadcast_update_source_codec(p_data); + if (restart_session) { +#if AHIM_ENABLED + btif_ahim_end_session(); + btif_ahim_init_hal(get_worker_thread(), BROADCAST); + btif_ahim_start_session(); +#else + btif_a2dp_source_restart_session(RawAddress::kEmpty, mBapBADevice); +#endif + btif_report_audio_config(bms_.AdvHandle(), current_config); + } + break; + case BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT: +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + BTIF_TRACE_WARNING("%s: SUSPEND_STEAM_REQ unhandled in Configured state", __func__); + break; + default: + BTIF_TRACE_WARNING("%s: unhandled event=%s", __func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + return false; + } + return true; + +} + +void BtifBapBroadcastStateMachine::StateStreaming::OnEnter() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::kFlagStreaming); + btif_report_broadcast_audio_state(bms_.AdvHandle(), BTBAP_BROADCAST__AUDIO_STATE_STARTED); +} + +void BtifBapBroadcastStateMachine::StateStreaming::OnExit() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); +} + +bool BtifBapBroadcastStateMachine::StateStreaming::ProcessEvent(uint32_t event, + void* p_data) { + BTIF_TRACE_IMP("[BapBroadcast]:%s: event = %s",__func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch (event) { + case BTIF_BAP_BROADCAST_DISABLE_EVT: + if (bms_.BIGState() == BIG_CREATED) { + bms_.SetBIGState(BIG_DISABLING); + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::KFlagDisabling); + if (bms_.CheckFlags(BtifBapBroadcaster::KFlagISOSetup)) { + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + } + } else { + bms_.SetBIGState(BIG_DISABLING); + BTIF_TRACE_DEBUG("[BapBroadcast]: BIG Terminate under process"); + } + break; + case BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT: + if (bms_.BIGState() != BIG_CREATED) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s: BIG is getting terminated already",__func__); +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + break; + } + bms_.SetFlags(BtifBapBroadcaster::kFlagISOPending); + total_bises = bms_.NumBises(); + current_iso_index = 0; + current_handle = bms_.BIGHandle(); + btif_bap_ba_remove_iso_datapath(current_handle); + + break; + case BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT: + { + char *p_p = (char *)p_data; + if (*p_p) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s: We shouldn't be in streaming state if ISO datapath is not setup yet",__func__); + } else { + if (bms_.CheckFlags(BtifBapBroadcaster::KFlagISOSetup)) { + BTIF_TRACE_WARNING("[BapBroadcast] We shouldn't be here, ISO Datapath is removed first before BIG"); + btif_bap_ba_remove_iso_datapath(bms_.BIGHandle()); + } else { + BTIF_TRACE_WARNING("[BapBroadcast]:%s:IsoDatapah is already removed",__func__); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + } + } + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT: + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + break; + case BTIF_BAP_BROADCAST_BISES_REMOVE_EVT: + bms_.ClearFlag(BtifBapBroadcaster::kFlagISOPending); + if (bms_.BIGState() == BIG_CREATED) + bms_.SetBIGState(BIG_TERMINATING); + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + break; + case BTIF_BAP_BROADCAST_BIG_REMOVED_EVT: + if (bms_.BIGState() == BIG_DISABLING) { + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_IDLE); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateIdle); + } else if (bms_.BIGState() == BIG_TERMINATING) { + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + } else if (bms_.BIGState() == BIG_RECONFIG) { +#if AHIM_ENABLED + btif_ahim_end_session(); + btif_ahim_init_hal(get_worker_thread(), BROADCAST); + btif_ahim_start_session(); +#else + btif_a2dp_source_restart_session(RawAddress::kEmpty, mBapBADevice); +#endif + btif_report_audio_config(bms_.AdvHandle(), current_config); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + break; + } +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + btif_bap_broadcast_update_source_codec(p_data); + if (restart_session) { + bms_.SetBIGState(BIG_RECONFIG); + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + } + break; + default: + BTIF_TRACE_WARNING("%s: unhandled event=%s", __func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + return false; + } + return true; +} + +static btif_ahim_client_callbacks_t sAhimBroadcastCallbacks = { + 2, // mode + btif_broadcast_process_hidl_event, + btif_bap_broadcast_get_sample_rate, + btif_bap_broadcast_get_ch_mode, + btif_bap_broadcast_get_bitrate, + btif_bap_broadcast_get_mtu, + btif_bap_broadcast_get_framelength, + btif_bap_broadcast_get_ch_count, + btif_bap_broadcast_is_simulcast_enabled, + nullptr, + nullptr, + nullptr +}; + +bt_status_t BtifBapBroadcastSource::Init(btbap_broadcast_callbacks_t* callbacks, + int max_broadcast, + btav_a2dp_codec_config_t codec_config,int mode) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + char value[PROPERTY_VALUE_MAX] = {'\0'}; + if (mode == 1) offload_enabled_ = true; + callbacks_ = callbacks; + default_config_ = codec_config; + memset(encryption_key.data(), 0, OCTET16_LEN); + init_local_capabilities(); + osi_property_get("persist.vendor.btstack.partial_simulcast",value,"false"); + if (strcmp(value, "true") == 0) { + BTIF_TRACE_IMP("[BapBroadcast]%s:Partial simulcast enabled",__func__); + mBisMultiplier = 2; + } else { + mBisMultiplier = 1; + } + osi_property_get("persist.vendor.btstack.transport_latency",value,"0"); + mBigParams.max_transport_latency = atoi(value); + osi_property_get("persist.vendor.btstack.bis_rtn",value,"2"); + mBigParams.rtn = atoi(value); + BTIF_TRACE_IMP("%s: transport_latency: %d, rtn: %d", + __func__, mBigParams.max_transport_latency, mBigParams.rtn); + BTIF_TRACE_IMP("%s: Fetch broadcast encryption key", __func__); + + size_t length = OCTET16_LEN; + bool ret = btif_config_get_bin("Adapter", "BAP_BA_ENC_KEY", encryption_key.data(), &length); + + if (!ret) { + btif_bap_ba_generate_enc_key_local(OCTET16_LEN); + } else { + reverseCode(encryption_key.data()); + if (isUnencrypted(encryption_key.data())) { + mEncryptionEnabled = false; + } + for (int i = 0; i < OCTET16_LEN; i++) { + BTIF_TRACE_IMP("[bapbroadcast]%s: encryption_key[%d] = %d",__func__,i,encryption_key[i]); + } + } +#if AHIM_ENABLED + reg_cb_with_ahim(BROADCAST, &sAhimBroadcastCallbacks); +#endif + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::EnableBroadcast(btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + current_config_ = codec_config; + btif_bap_ba_generate_broadcast_id(); + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::DisableBroadcast(int adv_handle) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + local_param[0] = adv_handle; + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_DISABLE_EVT, local_param)); + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::SetEncryption(int length) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + local_param[0] = length; + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT, local_param)); + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::SetBroadcastActive(bool setup, uint8_t adv_id) { + if (btif_a2dp_source_is_hal_v2_supported()) { + std::unique_lock guard(session_wait_); + mSession_wait = false; + if (setup) { + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT, (char *)&adv_id)); + } else { + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT,(char *)&adv_id)); + } + BTIF_TRACE_EVENT("%s: wating for signal",__func__); + session_wait_cv_.wait_for(guard, std::chrono::milliseconds(1000), + []{return mSession_wait;}); + BTIF_TRACE_EVENT("%s: done with signal",__func__); + return BT_STATUS_SUCCESS; + } + return BT_STATUS_SUCCESS; +} +void BtifBapBroadcastSource::Cleanup() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + //while(!bms_.empty()) { + for (auto it = bms_.begin();it != bms_.end();){ + BtifBapBroadcaster *bms = it->second; + auto prev_it = it++; + bms->Cleanup(); + bms_.erase(prev_it); + //delete bms; + } +} + +void BtifBapBroadcastSource::CleanupIdleBms() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + for (auto it = bms_.begin();it != bms_.end();){ + BtifBapBroadcaster *bms = it->second; + auto prev_it = it++; + if (bms->CanBeDeleted()) { + BTIF_TRACE_DEBUG("[BapBroadcast]%s: Cleaning up idle bms", __func__); + bms->Cleanup(); + bms_.erase(prev_it); + //delete bms; + } + //delete bms; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s:Exit",__func__); +} + +bt_status_t BtifBapBroadcastSource::SetUserConfig(uint8_t adv_handle, + btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + config_req_handle = (int) adv_handle; + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT, (char *)&codec_config)); + return BT_STATUS_SUCCESS; +} +BtifBapBroadcaster * BtifBapBroadcastSource::CreateBMS(int adv_handle) { + BtifBapBroadcaster *bms = new BtifBapBroadcaster(adv_handle, -1); +// bms_.insert(bms); + bms_.insert(std::make_pair(adv_handle, bms)); + bms->Init(); + return bms; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindBmsFromAdvHandle(uint8_t adv_handle) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: adv_handle = %d", __func__, adv_handle); + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->AdvHandle() == adv_handle) + return bms; + } + return nullptr; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindBmsFromBIGHandle(uint8_t big_handle) { + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->BIGHandle() == big_handle) + return bms; + } + return nullptr; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindStreamingBms() { + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->StateMachine().StateId() == BtifBapBroadcastStateMachine::kStateStreaming) + return bms; + } + return nullptr; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindConfiguredBms() { + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->StateMachine().StateId() == BtifBapBroadcastStateMachine::kStateConfigured) + return bms; + } + return nullptr; +} +BtifBapBroadcastSource::~BtifBapBroadcastSource(){} +/***************************************************************************** + * Local event handlers + *****************************************************************************/ +void print_config(btav_a2dp_codec_config_t config) { + BTIF_TRACE_WARNING("[BapBroadcast]%d: Sampling rate = %d", __func__,config.sample_rate); + BTIF_TRACE_WARNING("[BapBroadcast]%d: channel mode = %d", __func__, config.channel_mode); + BTIF_TRACE_WARNING("[BapBroadcast]%d: codec_specific_1 = %d", __func__, config.codec_specific_1); + BTIF_TRACE_WARNING("[BapBroadcast]%d: codec_specific_2 = %d", __func__, config.codec_specific_2); +} + +static void btif_report_encyption_key() { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->enc_key_cb, + std::string(reinterpret_cast(encryption_key.data()), OCTET16_LEN))); +} + +static void btif_report_broadcast_state(int adv_id, + btbap_broadcast_state_t state) { + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->broadcast_state_cb, + adv_id, state)); + } +} + +static void btif_report_broadcast_audio_state(int adv_id, + btbap_broadcast_audio_state_t state) { + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->audio_state_cb, + adv_id, state)); + } +} + +static void btif_report_audio_config(int adv_id, + btav_a2dp_codec_config_t codec_config) { + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->audio_config_cb, + adv_id, codec_config, broadcast_codecs_capabilities)); + } +} + +static void btif_report_setup_big(int setup, int adv_id, int big_handle, int num_bises) { + + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (bms == nullptr) return; + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->create_big_cb, setup, + adv_id, big_handle, num_bises, bms->GetBISHandles())); + } + +} + +static void btif_report_broadcast_id() { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->broadcast_id_cb, mBroadcastID)); +} + +static void btif_bap_ba_handle_event(uint32_t event, char* p_param) { + int big_handle, adv_id; + big_handle = adv_id = 0; + BtifBapBroadcaster *broadcaster; + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: event %s", + __func__, dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch(event) { + case BTIF_BAP_BROADCAST_DISABLE_EVT: + adv_id = (int)*p_param; + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:invalid index, Broadcast is already disabled",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT: + broadcaster = btif_bap_bms.FindConfiguredBms(); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT: + case BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT: + broadcaster = btif_bap_bms.FindStreamingBms(); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(config_req_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_CLEANUP_REQ_EVT: + broadcaster = btif_bap_bms.FindStreamingBms(); //TODO:add proper check + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT: + { + char *p_p = p_param; + int adv_handle = (int)*p_p; + BTIF_TRACE_ERROR("[BapBroadcast]:%s:adv_handle %d",__func__,adv_handle); + broadcaster = btif_bap_bms.CreateBMS((int)adv_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + broadcaster->SetAdvHandle(adv_handle); + BTIF_TRACE_ERROR("[BapBroadcast]:%s:adv_id = %d, big_handle = %d",__func__, adv_handle); + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT: + BTIF_TRACE_DEBUG("[BapBroadcast]:%s:End session", __func__); +#if AHIM_ENABLED + btif_ahim_end_session(); +#else + bluetooth::audio::a2dp::end_session(); +#endif + btif_bap_ba_signal_session_ready(); + return; + case BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT: + { + char *p_p = p_param; + int adv_handle = (int)*p_p; + BTIF_TRACE_DEBUG("[BapBroadcast]:%s:adv_handle = %d",__func__,adv_handle); + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(adv_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT: + { + char *p_p = p_param; + adv_id = *p_p++; + big_handle = *p_p; + broadcaster = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + } + break; + case BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT: + enc_keylength = (uint8_t)(*p_param); + notify_key_generated = true; + btif_bap_ba_generate_enc_key_local(enc_keylength); + return; + case BTIF_BAP_BROADCAST_BISES_SETUP_EVT: + case BTIF_BAP_BROADCAST_BISES_REMOVE_EVT: + big_handle = *p_param; + broadcaster = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_BIG_REMOVED_EVT: + adv_id = (int)*p_param; + btif_report_setup_big(0, adv_id,-1, 0); + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT: + btif_bap_process_request((tA2DP_CTRL_CMD ) *p_param); + return; + case BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT: + { + char *p = p_param; + uint8_t status = *p++; + uint16_t bis_handle = *p++; + bis_handle = (bis_handle | (*p <<8)); + btif_bap_ba_process_iso_setup(status, bis_handle); + return; + } + default: + BTIF_TRACE_ERROR("[BapBroadcast]:%s: invalid event = %d",__func__, event); + return; + } + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:Invalid broadcaster",__func__); + } + broadcaster->StateMachine().ProcessEvent(event, (void*)p_param); +} + +static void btif_bap_process_request(tA2DP_CTRL_CMD cmd) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + tA2DP_CTRL_ACK status = A2DP_CTRL_ACK_FAILURE; + //BtifBapBroadcaster *broadcaster; + uint32_t event = 0; +#if AHIM_ENABLED + btif_ahim_update_pending_command(cmd, BROADCAST); +#else + bluetooth::audio::a2dp::update_pending_command(cmd); +#endif + + switch(cmd) { + case A2DP_CTRL_CMD_START: + if (!bluetooth::headset::btif_hf_is_call_vr_idle()) { + status = A2DP_CTRL_ACK_INCALL_FAILURE; + break; + } + if (btif_bap_bms.FindStreamingBms() != nullptr) { + BTIF_TRACE_DEBUG("%s: Broadcast already streaming, crash recover(?)",__func__); + status = A2DP_CTRL_ACK_SUCCESS; + break; + } + if (btif_bap_bms.FindConfiguredBms() == nullptr) { + BTIF_TRACE_DEBUG("%s: Broadcast is disabled",__func__); + status = A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS; + break; + } + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT, NULL, 0); + event = BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT; + //broadcaster = btif_bap_bms.FindConfiguredBms(); + status = A2DP_CTRL_ACK_PENDING; + break; + case A2DP_CTRL_CMD_STOP: + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT, NULL, 0); + //broadcaster = btif_bap_bms.FindStreamingBms(); + status = A2DP_CTRL_ACK_SUCCESS; + break; + case A2DP_CTRL_CMD_SUSPEND: + if (btif_bap_bms.FindStreamingBms() != nullptr) { + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT, NULL, 0); + //broadcaster = btif_bap_bms.FindStreamingBms(); + status = A2DP_CTRL_ACK_PENDING; + } else { + status = A2DP_CTRL_ACK_SUCCESS; + } + break; + default: + APPL_TRACE_ERROR("UNSUPPORTED CMD (%d)", cmd); + status = A2DP_CTRL_ACK_FAILURE; + break; + } + // send the response now based on status + switch (cmd) { + case A2DP_CTRL_CMD_START: +#if AHIM_ENABLED + btif_ahim_ack_stream_started(status, BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(status); +#endif + break; + case A2DP_CTRL_CMD_SUSPEND: + case A2DP_CTRL_CMD_STOP: +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(status, BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(status); +#endif + break; + default: + break; + } + if (status != A2DP_CTRL_ACK_PENDING) { +#if AHIM_ENABLED + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::reset_pending_command(); +#endif + } +} + +static bool btif_bap_is_broadcaster_valid(uint8_t big_handle) { + BTIF_TRACE_DEBUG("[BapBroadcat]%s: handle = %d",__func__, big_handle); + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (bms == nullptr) return false; + if (pending_cmd == setup_iso) { + if (!bms->CheckFlags(BtifBapBroadcaster::kFlagISOPending) || + bms->StateMachine().StateId() != BtifBapBroadcastStateMachine::kStateConfigured) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s Broadcast disabled",__func__); + return false; + } + } else { + if (bms->BIGState() != BIG_CREATED) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s Broadcast disabled",__func__); + return false; + } + } + return true; +} + +static void btif_bap_ba_process_iso_setup(uint8_t status, uint16_t bis_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s",__func__); + if (!btif_bap_is_broadcaster_valid(current_handle)) return; + if (pending_cmd == setup_iso) { + local_param[0] = current_handle; + local_param[1] = status; + if (!btif_bap_ba_setup_iso_datapath(current_handle)) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s: notify bis setup",__func__); + pending_cmd = iso_unknown; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_BISES_SETUP_EVT, (char *)local_param); + } + } else if (pending_cmd == remove_iso) { + if (!btif_bap_ba_remove_iso_datapath(current_handle)) { + local_param[0] = current_handle; + local_param[1] = status; + pending_cmd = iso_unknown; + BTIF_TRACE_WARNING("[BapBroadcast]:%s: notify bis removed",__func__); + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_BISES_REMOVE_EVT, (char *)local_param); + } + } +} +static void btif_bap_ba_isodatapath_setup_cb(uint8_t status, uint16_t bis_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s, status = %d for handle = %d",__func__, status, bis_handle); + if (!btif_bap_is_broadcaster_valid(current_handle)) return; + if (pending_cmd == setup_iso) { + memset(local_param, 0, 3); + local_param[0] = status; + local_param[1] = bis_handle & 0x00FF; + local_param[2] = (bis_handle & 0xFF00) >> 8; + if (status == 0) { + total_bises--; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT, (char *)local_param); + } else { + local_param[0] = current_handle; + local_param[1] = status; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_BISES_SETUP_EVT, (char *)local_param); + } + } else if (pending_cmd == remove_iso) { + memset(local_param, 0, 3); + local_param[0] = status; + local_param[1] = bis_handle & 0x00FF; + local_param[2] = (bis_handle & 0xFF00) >> 8; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT, (char*)local_param); + } +} + +static bool btif_bap_ba_setup_iso_datapath(int big_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s",__func__); + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (bms == nullptr) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s bms is null",__func__); + return false; + } + if (!btif_bap_is_broadcaster_valid(big_handle)) return false; + if (current_iso_index == bms->NumBises()) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s completed",__func__); + return false; + } + pending_cmd = setup_iso; + std::vector BisHandles = bms->GetBISHandles(); + tBTM_BLE_SET_ISO_DATA_PATH_PARAM *p_param = new tBTM_BLE_SET_ISO_DATA_PATH_PARAM; + p_param->conn_handle = BisHandles[current_iso_index++]; + p_param->data_path_dir = 0; + p_param->data_path_id = PATH_ID;//6; + p_param->codec_id[0] = 6; + p_param->cont_delay[0] = 0; + p_param->cont_delay[1] = 0; + p_param->cont_delay[2] = 0; + p_param->codec_config_length = 0; + //param.codec_config = NULL; + p_param->p_cb = (tBTM_BLE_SETUP_ISO_DATA_PATH_CMPL_CB*)&btif_bap_ba_isodatapath_setup_cb; + + BTIF_TRACE_WARNING("[BapBroadcast]:%s for handle = %d",__func__, p_param->conn_handle); + do_in_bta_thread(FROM_HERE,base::Bind(base::IgnoreResult(&BTM_BleSetIsoDataPath), std::move(p_param))); + return true; +} + +static bool btif_bap_ba_remove_iso_datapath(int big_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s",__func__); + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (bms == nullptr) { + BTIF_TRACE_WARNING("[BapBroadcast]%s: broadcaster not found",__func__); + return false; + } + if (current_iso_index == bms->NumBises()) { + return false; + } + pending_cmd = remove_iso; + std::vector BisHandles = bms->GetBISHandles(); + uint16_t bis_handle = BisHandles[current_iso_index++]; + do_in_bta_thread(FROM_HERE, base::Bind(base::IgnoreResult(&BTM_BleRemoveIsoDataPath), bis_handle, + INPUT_DATAPATH,&btif_bap_ba_isodatapath_setup_cb)); + return true; +} + +void btif_bap_ba_creat_big_cb(uint8_t adv_id, uint8_t status, uint8_t big_handle, + uint32_t sync_delay, uint32_t transport_latency, uint8_t phy, uint8_t nse, uint8_t bn, uint8_t pto, + uint8_t irc, uint16_t max_pdu, uint16_t iso_int, uint8_t num_bis, std::vector conn_handle_list) { + BTIF_TRACE_IMP("[BapBroadcast]%s: callback: status = %d, adv_id = %d",__func__, status, adv_id); + if (status == BT_STATUS_SUCCESS) { + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (bms == nullptr) { + BTIF_TRACE_ERROR("%s: broadcaster not found",__func__); + return; + } + if (bms->StateMachine().StateId() != BtifBapBroadcastStateMachine::kStateConfigured || + bms->BIGState() != BIG_CREATING) { + BTIF_TRACE_WARNING("[BapBroadcast]%s: Broadcast is disabling",__func__); + return; + } + bms->SetBIGHandle(big_handle); + BTIF_TRACE_DEBUG("[BapBroadcast]%s: callback: big_handle = %d",__func__, bms->BIGHandle()); + bms->SetNumBises(num_bis); + BTIF_TRACE_DEBUG("[BapBroadcast]%s: callback: num_bis = %d",__func__, bms->NumBises()); + bms->SetBISHandles(conn_handle_list); + bms->SetBIGState(BIG_CREATED); + local_param[0] = adv_id; + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT, local_param)); + } else { + local_param[0] = adv_id; + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_DISABLE_EVT, local_param)); + } +} + +static void btif_bap_ba_create_big(int adv_handle) { + BTIF_TRACE_IMP("[BapBroadcast]:%s",__func__); + //char ba_enc[PROPERTY_VALUE_MAX] = {0}; +#if BIG_COMPILE + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromAdvHandle(adv_handle); + if (bms == nullptr) { + BTIF_TRACE_ERROR("%s: broadcaster not found",__func__); + return; + } + CreateBIGParameters param; + param.adv_handle = adv_handle; + param.num_bis = bms->NumBises(); + param.sdu_int = mBigParams.sdu_int; + param.max_sdu = mBigParams.max_sdu; + param.max_transport_latency = btif_bap_get_transport_latency(); + param.rtn = mBigParams.rtn; + param.phy = mBigParams.phy; + param.packing = mBigParams.packing;;//0 : sequential, 1: interleaved + param.framing = mBigParams.framing; + //osi_property_get("persist.bluetooth.ba_encryption", ba_enc, "true"); + //if (!(strncmp(ba_enc,"true",4))) { + if (mEncryptionEnabled) { + //mEncryptionEnabled = true; + param.encryption = 0x01; + } else { + mEncryptionEnabled = false; + param.encryption = 0x00; + } + uint8_t code[16] = {0}; + if (mEncryptionEnabled) { + memcpy(&code[0], encryption_key.data(),encryption_key.size()); + } + for(int i =0; i < 16; i++) { + param.broadcast_code.push_back(code[15-i]); + BTIF_TRACE_VERBOSE("[BapBroadcast]%s: code[%d] = %x, bc[%d] = %d",__func__,i,code[i],i,param.broadcast_code[i]); + } + + btif_gatt_get_interface()->advertiser->CreateBIG(adv_handle, param, + base::Bind(&btif_bap_ba_creat_big_cb)); +#endif /* BIG_COMPILE */ +} + +#if BIG_COMPILE +static void btif_bap_ba_terminate_big_cb(uint8_t status, uint8_t adv_id, + uint8_t big_handle, uint8_t reason) { + BTIF_TRACE_IMP("[BapBroadcast]:%s",__func__); + local_param[0] = adv_id; + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_BIG_REMOVED_EVT, /*(char*)&adv_id)*/local_param)); + +} +#endif /* BIG_COMPILE */ + +static void btif_bap_ba_terminate_big(int adv_handle, int big_handle) { + BTIF_TRACE_IMP("[BapBroadcast]:%s",__func__); +#if BIG_COMPILE + int reason = 0x16; //user terminated + btif_gatt_get_interface()->advertiser->TerminateBIG(adv_handle, big_handle, reason, + base::Bind(&btif_bap_ba_terminate_big_cb)); +#endif /* BIG_COMPILE */ +} + +void btif_bap_ba_dispatch_sm_event(btif_bap_broadcast_sm_event_t event, void* p_data, int len) { + BTIF_TRACE_DEBUG("%s: event: %d, len: %d", __FUNCTION__, event, len); + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, event, (char*)p_data)); + BTIF_TRACE_DEBUG("%s: event %d sent", __FUNCTION__, event); +} + +void btif_broadcast_process_hidl_event(tA2DP_CTRL_CMD cmd) { + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT, + (char*)&cmd, sizeof(cmd)); +} +bool btif_bap_broadcast_is_active() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + if (btif_bap_bms.BroadcastActive()) { + return true; + } + return false; +} + +void btif_bap_ba_update_big_params() { + uint32_t bitrate = btif_bap_broadcast_get_bitrate(); + mBigParams.max_sdu = btif_bap_broadcast_get_mtu(bitrate); + mBigParams.sdu_int = btif_bap_broadcast_get_framelength(); + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: max_sdu = %d, sdu_int = %d",__func__, + mBigParams.max_sdu, mBigParams.sdu_int); +} +uint16_t btif_bap_broadcast_get_sample_rate() { + if (current_config.sample_rate != BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: sample_rate = %d",__func__, current_config.sample_rate); + return current_config.sample_rate; + } else { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: default sample_rate = %d",__func__, default_config.sample_rate); + return default_config.sample_rate; + } +} +uint8_t btif_bap_broadcast_get_ch_mode() { + if (current_config.channel_mode != BTAV_A2DP_CODEC_CHANNEL_MODE_NONE) { + return current_config.channel_mode; + } else { + return default_config.channel_mode; + } +} +uint32_t btif_bap_broadcast_get_mtu(uint32_t bitrate) { + //based on bitrate set (100(80kbps), 120 (96kbps), 155 (128kbps)) + uint32_t mtu; + switch (bitrate) { + case 24000: + mtu = 30; + break; + case 27734: + mtu = 26; + break; + case 48000: {//HAP HQ + if (current_config.codec_specific_2 == 0) + mtu = 45; + else + mtu = 60; + break; + } + case 32000: { + if (current_config.codec_specific_2 == 0) + mtu = 30; + else + mtu = 40; + break; + } + case 64000: { + if (current_config.codec_specific_2 == 0) + mtu = 60; + else + mtu = 80; + break; + } + case 80000: { + if (current_config.codec_specific_2 == 0) + mtu = 75; + else + mtu = 100; + break; + } + case 95060: + mtu = 97; + break; + case 95550: + mtu = 130; + break; + case 96000: { + if (current_config.codec_specific_2 == 0) + mtu = 90; + else + mtu = 120; + break; + } + case 124800: + mtu = 117; + break; + case 124000: + mtu = 155; + break; + default: + mtu = 100; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: mtu = %d",__func__,mtu); + return mtu; +} + +uint16_t btif_bap_broadcast_get_framelength() { + uint16_t frame_duration; + switch (current_config.codec_specific_2) { + case 0: + frame_duration = 7500; //7.5msec + break; + case 1: + frame_duration = 10000; //10msec + break; + default: + frame_duration = 10000; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: bitrate = %d",__func__,frame_duration); + return frame_duration; +} + +uint32_t btif_bap_broadcast_get_bitrate() { + //based on bitrate set (100(80kbps), 120 (96kbps), 155 (128kbps)) + uint32_t bitrate = 0; + switch (current_config.codec_specific_1) { + case 1000: //32kbps + if (current_config.codec_specific_2 == 0) { + bitrate = 27734; + } else { + bitrate = 24000; + } + break; + case 1001: //32kbps + bitrate = 32000; + break; + case 1002: //48kbps + bitrate = 48000; + break; + case 1003: //64kbps + bitrate = 64000; + break; + case 1004: //80kbps + bitrate = 80000; + break; + case 1005: //955.55kbps + if (current_config.codec_specific_2 == 0) { + bitrate = 95060; + } else { + bitrate = 95550; + } + break; + case 1006: //96kbps + bitrate = 96000; + break; + case 1007: //96kbps + if (current_config.codec_specific_2 == 0) { + bitrate = 124800; + } else { + bitrate = 124000; + } + break; + default: + bitrate = 80000; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: bitrate = %d",__func__,bitrate); + return bitrate; +} + +uint8_t btif_bap_broadcast_get_ch_count() { + if (current_config.channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO) { + return BROADCAST_SPLIT_STEREO * mBisMultiplier; + } else if (current_config.channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_MONO || + current_config.channel_mode == BTBAP_CODEC_CHANNEL_MODE_JOINT_STEREO) { + return BROADCAST_MONO_JOINT * mBisMultiplier; + } + return BROADCAST_SPLIT_STEREO;//default split stereo +} + +static uint16_t btif_bap_get_transport_latency() { + uint16_t latency; + if (mBigParams.max_transport_latency != 0) { + BTIF_TRACE_DEBUG("[BapBroadcast]%s: latency set by property: %d", + __func__, mBigParams.max_transport_latency); + return mBigParams.max_transport_latency; + } + switch (current_config.codec_specific_2) { + case 0: + latency = 45;//45msec for 7.5msec frame duration + break; + case 1: + default: + latency = 61;//61msec for 10msec frame duration + break; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: transport latency = %d", + __func__, latency); + return latency; +} + +bool btif_bap_broadcast_is_simulcast_enabled() { + char value[PROPERTY_VALUE_MAX] = {'\0'}; + osi_property_get("persist.vendor.btstack.partial_simulcast",value,"false"); + if (strcmp(value, "true") == 0) { + BTIF_TRACE_IMP("[BapBroadcast]%s:Partial simulcast enabled",__func__); + return true; + } + return false; +} + +static void btif_bap_ba_copy_broadcast_id() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + for(int j = 0; j < 3; j++) { + BTIF_TRACE_DEBUG("Broadcast_ID[%d] = %d",j, mBroadcastID[j]); + } + btif_report_broadcast_id(); +} + +static void btif_bap_ba_generate_broadcast_id() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + btsnd_hcic_ble_rand(base::Bind([](BT_OCTET8 rand) { + for(int a = 0; a < 3; a++) { + uint8_t val = rand[a]; + BTIF_TRACE_DEBUG("val = %d", val); + mBroadcastID[a] = val; + } + btif_bap_ba_copy_broadcast_id(); + })); +} + +static void btif_bap_ba_generate_enc_key_local(int length) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s length = %d",__func__, length); + srand(time(NULL)); + int i = 0; + uint8_t random_key[OCTET16_LEN] = {0}; + while (i < length) { + uint8_t gen = (uint8_t)(rand() % 256); + uint8_t range = (gen % 75) + 48;//Alphanumeric range + if ((range > 57 && range < 65) || + (range > 90 && range < 97)) { + //Ascii range 58 to 64 + //:,;, <, =, >, ?, @] + //range 91 to 96 + //[, \, ], ^, _, ` + BTIF_TRACE_DEBUG("Generate key: Invalid character"); + continue; + } + random_key[i] = range; + i++; + } + + memset(encryption_key.data(), 0, OCTET16_LEN); + memcpy(encryption_key.data(),random_key, OCTET16_LEN); + reverseCode(random_key); + BTIF_TRACE_DEBUG("[BapBroadcast]:%s storing new excryption key of length %d",__func__, OCTET16_LEN); + if (btif_config_set_bin("Adapter", "BAP_BA_ENC_KEY", /*encryption_key.data()*/random_key, OCTET16_LEN)) { + BTIF_TRACE_DEBUG("%s: stored new key", __func__); + btif_config_flush(); + } else { + BTIF_TRACE_DEBUG("%s: failed to store new key", __func__);; + } + if (notify_key_generated) { + notify_key_generated = false; + btif_report_encyption_key(); + } +} +void init_local_capabilities() { + broadcast_codecs_capabilities.push_back(broadcast_local_capability); +} + +static bt_status_t init_broadcast( + btbap_broadcast_callbacks_t* callbacks, + int max_broadcast, btav_a2dp_codec_config_t codec_config, int mode) { + if(max_broadcast > BTIF_BAP_BA_NUM_CB) { + BTIF_TRACE_WARNING("%s: App setting maximum allowable broadcast(%d) \ + to more than limit(%d)", + __func__, max_broadcast, BTIF_BAP_BA_NUM_CB); + max_broadcast = BTIF_BAP_BA_NUM_CB; + } + return btif_bap_bms.Init(callbacks, max_broadcast, codec_config, mode); +} +static bt_status_t enable_broadcast(btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_IMP("[BapBroadcast]:%s", __func__); + current_config = codec_config; + print_config(current_config); + return btif_bap_bms.EnableBroadcast(codec_config); +} +static bt_status_t disable_broadcast(int adv_id) { + BTIF_TRACE_IMP("[BapBroadcast]:%s", __func__); + return btif_bap_bms.DisableBroadcast(adv_id); +} +static bt_status_t set_broadcast_active(bool setup, uint8_t adv_id) { + BTIF_TRACE_EVENT("[BapBroadcast]:%s", __func__); + return btif_bap_bms.SetBroadcastActive(setup, adv_id); +} +static bt_status_t config_codec(uint8_t adv_handle, btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_IMP("[BapBroadcast]:%s", __func__); + bool config_changed = false; + print_config(codec_config); + if (codec_config.sample_rate != current_config.sample_rate || + codec_config.channel_mode != current_config.channel_mode || + codec_config.codec_specific_1 != current_config.codec_specific_1 || + codec_config.codec_specific_2 != current_config.codec_specific_2 || + codec_config.codec_specific_4 > 0) { + config_changed = true; + } + + if (config_changed) { + return btif_bap_bms.SetUserConfig(adv_handle, codec_config); + } + return BT_STATUS_SUCCESS; +} +static bt_status_t set_encryption(bool enabled, uint8_t enc_length) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: length %d", __func__, enc_length); + mEncryptionEnabled = enabled; + /*if (!mEncryptionEnabled) { + btif_config_remove("Adapter", "BAP_BA_ENC_KEY"); + return BT_STATUS_SUCCESS; + }*/ + return btif_bap_bms.SetEncryption(enc_length); +} + +static std::string get_encryption_key() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + return std::string(reinterpret_cast(encryption_key.data()), OCTET16_LEN); +} + +static bt_status_t setup_audio_data_path(bool enable, uint8_t adv_id, + uint8_t big_handle, int num_bises, int *bis_handles) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + return BT_STATUS_SUCCESS; +} +static void cleanup_broadcast() { + BTIF_TRACE_ERROR("[BapBroadcast]:%s", __func__); + btif_bap_bms.Cleanup(); +} + +static const btbap_broadcast_interface_t bt_bap_broadcast_src_interface = { + sizeof(btbap_broadcast_interface_t), + init_broadcast, + enable_broadcast, + disable_broadcast, + set_broadcast_active, + config_codec, + setup_audio_data_path, + get_encryption_key, + set_encryption, + cleanup_broadcast, +}; + +/******************************************************************************* + * + * Function btif_bap_broadcast_get_interface + * + * Description Get the Bap broadcast callback interface + * + * Returns btbap_broadcast_interface_t + * + ******************************************************************************/ +const btbap_broadcast_interface_t* btif_bap_broadcast_get_interface(void) { + BTIF_TRACE_EVENT("%s", __func__); + return &bt_bap_broadcast_src_interface; +} + + diff --git a/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc b/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc new file mode 100644 index 0000000000000000000000000000000000000000..447c3f62e2b6af2310fb68438adc5ecd93dda5eb --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc @@ -0,0 +1,395 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include "bta_closure_api.h" +#include "bta_bap_uclient_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "osi/include/thread.h" + +// Capabilities to be stored on CodecConfig structure : +// Supported_Sampling_Frequencies  ( codec config sampling rate ) +// Audio_Channel_Counts ( codec config channel mode ) +// Supported_Frame_Durations ( 1st byte of codec specific 1 )  +// Max_Supported_LC3_Frames_Per_SDU  ( 2nd byte of codec specific 1) +// Preferred_Audio_Contexts ( 3&4 bytes of codec specific 1 ) +// Supported_Octets_Per_Codec_Frame ( first 4 bytes codec specific 2 ) +/* Vendor_Specific Metadata ( first 4 bytes of codec specific 3) + * 1st byte conveys LC3Q support, + * 2nd byte conveys LC3Q version + */ + +// Configurations to be stored in CodecConfig structure : +// Sampling_Frequency  ( codec config sampling rate ) +// Audio_Channel_Allocation ( codec config channel mode ) +// Frame_Duration  ( 5th bye of codec specific 1 )  +// LC3_Blocks_Per_SDU ( 6th byte of codec specific 1 ) +// Preferred_Audio_Contexts ( 7&8 bytes of codec specific 1 ) +// Octets_Per_Codec_Frame ( 2 bytes  )  ( 5&6th bytes of codec specific 2 ) +// LC3Q preferred (1 byte) ( 7th byte of codec specific 2 ) +/* Vendor_Specific Metadata ( first 4 bytes of codec specific 3) + * 1st byte conveys LC3Q support, + * 2nd byte conveys LC3Q version + */ + +#include +#include +#include + +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::CodecChannelMode; + +constexpr uint8_t CAPA_SUP_FRAME_DUR_INDEX = 0x00; // CS1 +constexpr uint8_t CAPA_MAX_SUP_LC3_FRAMES_INDEX = 0x01; // CS1 +constexpr uint8_t CAPA_PREF_AUDIO_CONT_INDEX = 0x02; // CS1 +constexpr uint8_t CAPA_SUP_OCTS_PER_FRAME_INDEX = 0x00; // CS2 + +constexpr uint8_t CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX = 0x00; // CS3 +constexpr uint8_t CAPA_VENDOR_METADATA_LC3Q_VER_INDEX = 0x01; // CS3 + +constexpr uint8_t CONFIG_FRAME_DUR_INDEX = 0x04; // CS1 +constexpr uint8_t CONFIG_LC3_BLOCKS_INDEX = 0x05; // CS1 +constexpr uint8_t CONFIG_PREF_AUDIO_CONT_INDEX = 0x06; // CS1 +constexpr uint8_t CONFIG_OCTS_PER_FRAME_INDEX = 0x04; // CS2 +constexpr uint8_t CONFIG_LC3Q_PREF_INDEX = 0x06; // CS2 +constexpr uint8_t CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX = 0x00; // CS3 +constexpr uint8_t CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX = 0x01; // CS3 + +// capabilities +bool UpdateCapaSupFrameDurations(CodecConfig *config , uint8_t sup_frame) { + config->codec_specific_1 &= ~(0xFF << (CAPA_SUP_FRAME_DUR_INDEX * 8)); + config->codec_specific_1 |= sup_frame << (CAPA_SUP_FRAME_DUR_INDEX * 8); + return true; +} + +bool UpdateCapaMaxSupLc3Frames(CodecConfig *config, + uint8_t max_sup_lc3_frames) { + config->codec_specific_1 &= ~(0xFF << (CAPA_MAX_SUP_LC3_FRAMES_INDEX * 8)); + config->codec_specific_1 |= max_sup_lc3_frames << + (CAPA_MAX_SUP_LC3_FRAMES_INDEX * 8); + return true; +} + +bool UpdateCapaPreferredContexts(CodecConfig *config, uint16_t contexts) { + config->codec_specific_1 &= ~(0xFFFF << (CAPA_PREF_AUDIO_CONT_INDEX * 8)); + config->codec_specific_1 |= contexts << (CAPA_PREF_AUDIO_CONT_INDEX * 8); + return true; +} + +bool UpdateCapaSupOctsPerFrame(CodecConfig *config, + uint32_t octs_per_frame) { + config->codec_specific_2 &= ~(0xFFFFFFFF << + (CAPA_SUP_OCTS_PER_FRAME_INDEX * 8)); + config->codec_specific_2 |= octs_per_frame << + (CAPA_SUP_OCTS_PER_FRAME_INDEX * 8); + return true; +} + +bool UpdateCapaVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref) { + config->codec_specific_3 &= ~(0xFF << (CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX * 8)); + config->codec_specific_3 |= lc3q_pref << (CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX * 8); + return true; +} + +bool UpdateCapaVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver) { + config->codec_specific_3 &= ~(0xFF << (CAPA_VENDOR_METADATA_LC3Q_VER_INDEX * 8)); + config->codec_specific_3 |= lc3q_ver << (CAPA_VENDOR_METADATA_LC3Q_VER_INDEX * 8); + return true; +} + +uint8_t GetCapaSupFrameDurations(CodecConfig *config) { + return (config->codec_specific_1 >> (8*CAPA_SUP_FRAME_DUR_INDEX)) & 0xff; +} + +uint8_t GetCapaMaxSupLc3Frames(CodecConfig *config) { + if (((config->codec_specific_1 >> + (8*CAPA_MAX_SUP_LC3_FRAMES_INDEX)) & 0xff) == 0x0) { + uint8_t max_chnl_count = 0; + LOG(ERROR) << __func__ + << ": Max Sup LC3 frames is 0, deriving based on chnl count"; + if(static_cast (config->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_STEREO)) { + max_chnl_count = 2; + } else if(static_cast (config->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_MONO)) { + max_chnl_count = 1; + } + return max_chnl_count; + } else { + return (config->codec_specific_1 >> + (8*CAPA_MAX_SUP_LC3_FRAMES_INDEX)) & 0xff; + } +} + +uint16_t GetCapaPreferredContexts(CodecConfig *config) { + return (config->codec_specific_1 >> + (8*CAPA_PREF_AUDIO_CONT_INDEX)) & 0xffff; +} + +uint32_t GetCapaSupOctsPerFrame(CodecConfig *config) { + return (config->codec_specific_2 >> + (8*CAPA_SUP_OCTS_PER_FRAME_INDEX)) & 0xffffffff; +} + +bool GetCapaVendorMetaDataLc3QPref(CodecConfig *config) { + if (((config->codec_specific_3 >> + (8*CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX)) & 0xff) == 0x0) { + return false; + } else + return true; +} + +uint8_t GetCapaVendorMetaDataLc3QVer(CodecConfig *config) { + return (config->codec_specific_3 >> + (8*CAPA_VENDOR_METADATA_LC3Q_VER_INDEX)) & 0xff; +} + +// Configurations +bool UpdateFrameDuration(CodecConfig *config , uint8_t frame_dur) { + uint64_t value = 0xFF; + config->codec_specific_1 &= ~(value << (CONFIG_FRAME_DUR_INDEX*8)); + config->codec_specific_1 |= static_cast(frame_dur) << + (CONFIG_FRAME_DUR_INDEX * 8); + return true; +} + +bool UpdateLc3BlocksPerSdu(CodecConfig *config, uint8_t lc3_blocks_per_sdu) { + uint64_t value = 0xFF; + config->codec_specific_1 &= ~(value << (CONFIG_LC3_BLOCKS_INDEX * 8)); + config->codec_specific_1 |= static_cast(lc3_blocks_per_sdu) << + (CONFIG_LC3_BLOCKS_INDEX * 8); + return true; +} + +bool UpdatePreferredAudioContext(CodecConfig *config , + uint16_t pref_audio_context) { + uint64_t value = 0xFFFF; + config->codec_specific_1 &= ~(value << (CONFIG_PREF_AUDIO_CONT_INDEX*8)); + config->codec_specific_1 |= static_cast(pref_audio_context) << + (CONFIG_PREF_AUDIO_CONT_INDEX * 8); + return true; +} + +bool UpdateOctsPerFrame(CodecConfig *config , uint16_t octs_per_frame) { + uint64_t value = 0xFFFF; + config->codec_specific_2 &= ~(value << (CONFIG_OCTS_PER_FRAME_INDEX * 8)); + config->codec_specific_2 |= + static_cast(octs_per_frame) << + (CONFIG_OCTS_PER_FRAME_INDEX * 8); + return true; +} + +bool UpdateLc3QPreference(CodecConfig *config , bool lc3q_pref) { + uint64_t value = 0xFF; + config->codec_specific_2 &= ~(value << (CONFIG_LC3Q_PREF_INDEX * 8)); + config->codec_specific_2 |= + static_cast(lc3q_pref) << + (CONFIG_LC3Q_PREF_INDEX * 8); + LOG(WARNING) << __func__ + << ": lc3q_pref cs2: " << loghex(config->codec_specific_2); + return true; +} + + +bool UpdateVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref) { + uint64_t value = 0xFF; + config->codec_specific_3 &= ~(value << (CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX * 8)); + config->codec_specific_3 |= static_cast(lc3q_pref) << + (CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX * 8); + return true; +} + +bool UpdateVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver) { + uint64_t value = 0xFF; + config->codec_specific_3 &= ~(value << (CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX * 8)); + config->codec_specific_3 |= static_cast(lc3q_ver) << + (CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX * 8); + return true; +} + +uint8_t GetFrameDuration(CodecConfig *config) { + return (config->codec_specific_1 >> (8*CONFIG_FRAME_DUR_INDEX)) & 0xff; +} + +uint8_t GetLc3BlocksPerSdu(CodecConfig *config) { + return (config->codec_specific_1 >> (8*CONFIG_LC3_BLOCKS_INDEX)) & 0xff; +} + +uint16_t GetPreferredAudioContext(CodecConfig *config) { + return (config->codec_specific_1 >> + (8*CONFIG_PREF_AUDIO_CONT_INDEX)) & 0xffff; +} + +uint16_t GetOctsPerFrame(CodecConfig *config) { + return (config->codec_specific_2 >> (8*CONFIG_OCTS_PER_FRAME_INDEX)) & 0xffff; +} + +uint8_t GetLc3QPreference(CodecConfig *config) { + LOG(WARNING) << __func__ << ": lc3q_pref cs2: " + << loghex((config->codec_specific_2 >> (8*CONFIG_LC3Q_PREF_INDEX)) & 0xff); + return (config->codec_specific_2 >> + (8*CONFIG_LC3Q_PREF_INDEX)) & 0xff; +} + +uint8_t GetVendorMetaDataLc3QPref(CodecConfig *config) { + return (config->codec_specific_3 >> + (8*CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX)) & 0xff; +} + +uint8_t GetVendorMetaDataLc3QVer(CodecConfig *config) { + return (config->codec_specific_3 >> + (8*CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX)) & 0xff; +} + +bool IsCodecConfigEqual(CodecConfig *src_config, CodecConfig *dst_config) { + // first check if passed codec configs are configurations or + // capabilities using first byte of codec specific 1 + if(src_config == nullptr || dst_config == nullptr) { + return false; + } + + bool is_src_capability = src_config->codec_specific_1 & 0XFF; + bool is_dst_capability = dst_config->codec_specific_1 & 0XFF; + + // check the codec type + if(src_config->codec_type != dst_config->codec_type) { + LOG(ERROR) << __func__ << ": No match for codec type "; + return false; + } + + // check sample rate + if(src_config->sample_rate != dst_config->sample_rate) { + LOG(ERROR) << __func__ << ": No match for sample rate"; + return false; + } + + // check channel mode + if(!(static_cast(src_config->channel_mode) & + static_cast(dst_config->channel_mode))) { + LOG(ERROR) << __func__ << ": No match for channel mode "; + return false; + } + + LOG(WARNING) << __func__ + << ": is_src_capability: " << loghex(is_src_capability) + << ", is_dst_capability: " << loghex(is_dst_capability); + + if(is_src_capability && is_dst_capability) { + if(src_config->codec_specific_1 != dst_config->codec_specific_1 || + src_config->codec_specific_2 != dst_config->codec_specific_2 || + src_config->codec_specific_3 != dst_config->codec_specific_3) { + LOG(WARNING) << __func__ << ": No match for CS params. "; + return false; + } + } else if (!is_src_capability && !is_dst_capability) { + LOG(INFO) << __func__ << ": Comparison for both configs "; + uint8_t src_frame_dur = GetFrameDuration(src_config); + uint8_t src_lc3_blocks_per_sdu = GetLc3BlocksPerSdu(src_config); + uint16_t src_octs_per_frame = GetOctsPerFrame(src_config); + uint8_t src_lc3q_pref = GetLc3QPreference(src_config); + uint16_t src_pref_audio_context = GetPreferredAudioContext(src_config); + + uint8_t dst_frame_dur = GetFrameDuration(dst_config); + uint8_t dst_lc3_blocks_per_sdu = GetLc3BlocksPerSdu(dst_config); + uint16_t dst_octs_per_frame = GetOctsPerFrame(dst_config); + uint8_t dst_lc3q_pref = GetLc3QPreference(dst_config); + uint16_t dst_pref_audio_context = GetPreferredAudioContext(dst_config); + + if(src_frame_dur != dst_frame_dur) { + LOG(ERROR) << __func__ << ": Frame Dur not match with existing config "; + return false; + } + + if(src_lc3_blocks_per_sdu != dst_lc3_blocks_per_sdu) { + LOG(ERROR) << __func__ << ": Lc3 blocks not match with existing config "; + return false; + } + + if(src_octs_per_frame != dst_octs_per_frame) { + LOG(ERROR) << __func__ << ": Octs per frame not match with existing config "; + return false; + } + + if(src_lc3q_pref != dst_lc3q_pref) { + LOG(ERROR) << __func__ << ": Lc3Q pref not match with existing config "; + return false; + } + + if (!(src_pref_audio_context & dst_pref_audio_context)) { + LOG(ERROR) << __func__ << ": pref_audio_context not match with existing config "; + return false; + } + + } else if(is_src_capability || is_dst_capability) { + CodecConfig *capa_config = is_src_capability ? src_config:dst_config; + CodecConfig *oth_config = is_src_capability ? dst_config:src_config; + + uint8_t capa_sup_frames = GetCapaSupFrameDurations(capa_config); + uint8_t capa_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(capa_config); + uint16_t capa_min_sup_octs = GetCapaSupOctsPerFrame(capa_config) & 0xFFFF; + uint16_t capa_max_sup_octs = (GetCapaSupOctsPerFrame(capa_config) + & 0xFFFF0000) >> 16; + bool capa_lc3q_pref = GetCapaVendorMetaDataLc3QPref(capa_config); + uint16_t capa_pref_audio_context = GetCapaPreferredContexts(capa_config); + + uint8_t frame_dur = GetFrameDuration(oth_config); + uint8_t lc3_blocks_per_sdu = GetLc3BlocksPerSdu(oth_config); + uint16_t octs_per_frame = GetOctsPerFrame(oth_config); + uint8_t lc3q_pref = GetLc3QPreference(oth_config); + uint16_t dst_pref_audio_context = GetPreferredAudioContext(oth_config); + + LOG(WARNING) << __func__ + << ": capa_sup_frames: " << loghex(capa_sup_frames) + << ", frame_dur: " << loghex(frame_dur); + + LOG(WARNING) << __func__ + << ": capa_lc3q_pref: " << capa_lc3q_pref + << ", lc3q_pref: " << loghex(lc3q_pref); + + LOG(WARNING) << __func__ + << ": capa_pref_audio_context: " << capa_pref_audio_context + << ", dst_pref_audio_context: " << dst_pref_audio_context; + + if(!(capa_sup_frames & (0x01 << frame_dur))) { + LOG(ERROR) << __func__ << ": No match for frame duration "; + return false; + } + + if(capa_max_sup_lc3_frames && lc3_blocks_per_sdu) { + if(capa_max_sup_lc3_frames < lc3_blocks_per_sdu * + static_cast (oth_config->channel_mode)) { + LOG(ERROR) << __func__ << ": blocks per sdu exceeds the capacity "; + return false; + } + } + + if( octs_per_frame < capa_min_sup_octs || + octs_per_frame > capa_max_sup_octs) { + LOG(ERROR) << __func__ << ": octs per frame not in limits "; + return true; + } + + if (!(capa_pref_audio_context & dst_pref_audio_context)) { + LOG(ERROR) << __func__ << ": No Match for Audio context"; + return false; + } + + } + return true; +} diff --git a/le_audio/system/bt/btif/src/btif_bap_config.cc b/le_audio/system/bt/btif/src/btif_bap_config.cc new file mode 100644 index 0000000000000000000000000000000000000000..6daeb6ad6557ebc212c0e021167408ef200fdf8f --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_config.cc @@ -0,0 +1,860 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright (C) 2014 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#define LOG_TAG "bt_btif_bap_config" + +#include "btif_bap_config.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "bt_types.h" +#include "btcore/include/module.h" +#include "btif_api.h" +#include "btif_common.h" +#include "btif_util.h" +#include "osi/include/alarm.h" +#include "osi/include/allocator.h" +#include "osi/include/compat.h" +#include "osi/include/config.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "btif_bap_codec_utils.h" + +#define BT_CONFIG_SOURCE_TAG_NUM 1010001 + +#define INFO_SECTION "Info" +#define FILE_TIMESTAMP "TimeCreated" +#define FILE_SOURCE "FileSource" +#define TIME_STRING_LENGTH sizeof("YYYY-MM-DD HH:MM:SS") +#define INDEX_FREE (0x00) +#define INDEX_OCCUPIED (0x01) +#define MAX_INDEX (255) +#define MAX_INDEX_LEN (0x04) +#define MAX_SECTION_LEN (255) + +using bluetooth::bap::pacs::CodecSampleRate; + +static const char* TIME_STRING_FORMAT = "%Y-%m-%d %H:%M:%S"; + +// TODO(armansito): Find a better way than searching by a hardcoded path. +#if defined(OS_GENERIC) +static const char* CONFIG_FILE_PATH = "bap_config.conf"; +static const char* CONFIG_BACKUP_PATH = "bap_config.bak"; +#else // !defined(OS_GENERIC) +static const char* CONFIG_FILE_PATH = "/data/misc/bluedroid/bap_config.conf"; +static const char* CONFIG_BACKUP_PATH = "/data/misc/bluedroid/bap_config.bak"; +#endif // defined(OS_GENERIC) +static const period_ms_t CONFIG_SETTLE_PERIOD_MS = 3000; + +static void timer_config_save_cb(void* data); +static void btif_bap_config_write(uint16_t event, char* p_param); +static bool is_factory_reset(void); +static void delete_config_files(void); +static void btif_bap_config_remove_restricted(config_t* config); +static config_t* btif_bap_config_open(const char* filename); + +static enum ConfigSource { + NOT_LOADED, + ORIGINAL, + BACKUP, + NEW_FILE, + RESET +} btif_bap_config_source = NOT_LOADED; + +//static int btif_bap_config_devices_loaded = -1; +static char btif_bap_config_time_created[TIME_STRING_LENGTH]; + +static config_t* config; +static std::recursive_mutex config_lock; // protects operations on |config|. +static alarm_t* config_timer; + +#define BAP_DIRECTION_KEY "Direction" +#define BAP_CODEC_TYPE_KEY "CodecType" + +#define BAP_RECORD_TYPE_KEY "RecordType" +#define BAP_RECORD_TYPE_CAPA "Capability" +#define BAP_RECORD_TYPE_CONF "Configuration" + +#define BAP_SAMP_FREQS_KEY "SamplingRate" +#define BAP_CONTEXT_TYPE_KEY "ContextType" + +#define BAP_SUPP_FRM_DURATIONS_KEY "SupFrameDurations" +#define BAP_SUP_MIN_OCTS_PER_FRAME_KEY "SupMinOctsPerFrame" +#define BAP_SUP_MAX_OCTS_PER_FRAME_KEY "SupMaxOctsPerFrame" +#define BAP_MAX_SUP_CODEC_FRAMES_PER_SDU "SupMaxFramesPerSDU" +#define BAP_LC3Q_SUP_KEY "LC3QSupport" +#define BAP_LC3Q_VER_KEY "LC3QVersion" + +#define BAP_CONF_FRAME_DUR_KEY "ConfiguredFrameDur" +#define BAP_CONF_OCTS_PER_FRAME_KEY "ConfiguredOctsPerFrame" +#define BAP_LC3_FRAMES_PER_SDU_KEY "Lc3FramesPerSDU" +#define BAP_CHNL_ALLOCATION_KEY "ChannelAllocation" + +#define BAP_SRC_LOCATIONS_KEY "SrcLocation" +#define BAP_SINK_LOCATIONS_KEY "SinkLocation" +#define BAP_SUP_AUDIO_CONTEXTS_KEY "SupAudioContexts" + +// Module lifecycle functions +static future_t* init(void) { + std::unique_lock lock(config_lock); + + if (is_factory_reset()) delete_config_files(); + + std::string file_source; + + config = btif_bap_config_open(CONFIG_FILE_PATH); + btif_bap_config_source = ORIGINAL; + if (!config) { + LOG_WARN(LOG_TAG, "%s unable to load config file: %s; using backup.", + __func__, CONFIG_FILE_PATH); + config = btif_bap_config_open(CONFIG_BACKUP_PATH); + btif_bap_config_source = BACKUP; + file_source = "Backup"; + } + + if (!config) { + LOG_ERROR(LOG_TAG, + "%s unable to transcode legacy file; creating empty config.", + __func__); + config = config_new_empty(); + btif_bap_config_source = NEW_FILE; + file_source = "Empty"; + } + + if (!config) { + LOG_ERROR(LOG_TAG, "%s unable to allocate a config object.", __func__); + goto error; + } + + if (!file_source.empty()) + config_set_string(config, INFO_SECTION, FILE_SOURCE, file_source.c_str()); + + // Cleanup temporary pairings if we have left guest mode + if (!is_restricted_mode()) btif_bap_config_remove_restricted(config); + + // Read or set config file creation timestamp + const char* time_str; + time_str = config_get_string(config, INFO_SECTION, FILE_TIMESTAMP, NULL); + if (time_str != NULL) { + strlcpy(btif_bap_config_time_created, time_str, TIME_STRING_LENGTH); + } else { + time_t current_time = time(NULL); + struct tm* time_created = localtime(¤t_time); + if (time_created) { + if (strftime(btif_bap_config_time_created, TIME_STRING_LENGTH, + TIME_STRING_FORMAT, time_created)) { + config_set_string(config, INFO_SECTION, FILE_TIMESTAMP, + btif_bap_config_time_created); + } + } + } + // TODO(sharvil): use a non-wake alarm for this once we have + // API support for it. There's no need to wake the system to + // write back to disk. + config_timer = alarm_new("btif_bap.config"); + if (!config_timer) { + LOG_ERROR(LOG_TAG, "%s unable to create alarm.", __func__); + goto error; + } + + LOG_EVENT_INT(BT_CONFIG_SOURCE_TAG_NUM, btif_bap_config_source); + + return future_new_immediate(FUTURE_SUCCESS); + +error: + alarm_free(config_timer); + if (config != NULL) + config_free(config); + config_timer = NULL; + config = NULL; + btif_bap_config_source = NOT_LOADED; + return future_new_immediate(FUTURE_FAIL); +} + +static config_t* btif_bap_config_open(const char* filename) { + config_t* config = config_new(filename); + if (!config) return NULL; + + return config; +} + +static void btif_bap_config_save(void) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + if (config_timer == NULL) { + LOG(WARNING) << __func__ << "config_timer is null"; + return; + } + alarm_set(config_timer, CONFIG_SETTLE_PERIOD_MS, timer_config_save_cb, NULL); +} + +static void btif_bap_config_flush(void) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + alarm_cancel(config_timer); + btif_bap_config_write(0, NULL); +} + +bool btif_bap_config_clear(void) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + alarm_cancel(config_timer); + + std::unique_lock lock(config_lock); + if (config != NULL) + config_free(config); + + config = config_new_empty(); + if (config == NULL) return false; + + bool ret = config_save(config, CONFIG_FILE_PATH); + btif_bap_config_source = RESET; + return ret; +} + +static future_t* shut_down(void) { + btif_bap_config_flush(); + return future_new_immediate(FUTURE_SUCCESS); +} + +static future_t* clean_up(void) { + btif_bap_config_flush(); + + alarm_free(config_timer); + config_timer = NULL; + + std::unique_lock lock(config_lock); + config_free(config); + config = NULL; + return future_new_immediate(FUTURE_SUCCESS); +} + +EXPORT_SYMBOL module_t btif_bap_config_module = {.name = BTIF_BAP_CONFIG_MODULE, + .init = init, + .start_up = NULL, + .shut_down = shut_down, + .clean_up = clean_up}; + +static void timer_config_save_cb(UNUSED_ATTR void* data) { + // Moving file I/O to btif context instead of timer callback because + // it usually takes a lot of time to be completed, introducing + // delays during A2DP playback causing blips or choppiness. + btif_transfer_context(btif_bap_config_write, 0, NULL, 0, NULL); +} + +static void btif_bap_config_write(UNUSED_ATTR uint16_t event, + UNUSED_ATTR char* p_param) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + std::unique_lock lock(config_lock); + rename(CONFIG_FILE_PATH, CONFIG_BACKUP_PATH); + if (config == NULL) { + LOG(WARNING) << __func__ << "config is null"; + return; + } + config_t* config_paired = config_new_clone(config); + + if (config_paired != NULL) { + //btif_bap_config_remove_unpaired(config_paired); + config_save(config_paired, CONFIG_FILE_PATH); + config_free(config_paired); + } +} + +static void btif_bap_config_remove_restricted(config_t* config) { + CHECK(config != NULL); + + if (config == NULL) { + LOG(WARNING) << __func__ << "config is null"; + return; + } + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char* section = config_section_name(snode); + // first check the address + if (config_has_key(config, section, "Restricted")) { + config_remove_section(config, section); + } + } +} + +static bool is_factory_reset(void) { + char factory_reset[PROPERTY_VALUE_MAX] = {0}; + osi_property_get("persist.bluetooth.factoryreset", factory_reset, "false"); + return strncmp(factory_reset, "true", 4) == 0; +} + +static void delete_config_files(void) { + remove(CONFIG_FILE_PATH); + remove(CONFIG_BACKUP_PATH); + osi_property_set("persist.bluetooth.factoryreset", "false"); +} + +static bool btif_bap_get_section_index(const std::string §ion, + uint16_t *index) { + char *temp = nullptr; + if (section.length() != 20) return false; + + std::vector byte_tokens = + base::SplitString(section, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + + LOG(WARNING) << __func__ << ": LC# codec "; + if (byte_tokens.size() != 7) return false; + + // get the last nibble + const auto& token = byte_tokens[6]; + + if (token.length() != 2) return false; + + *index = strtol(token.c_str(), &temp, 16); + + if (*temp != '\0') return false; + + return true; +} + +static bool btif_bap_get_free_section_id(const RawAddress& bd_addr, + char *section) { + uint16_t i = 0; + uint8_t index_status[MAX_INDEX] = {0}; + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + + // reserve the first index for sink, src, locations + index_status[0] = INDEX_OCCUPIED; + + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char* section = config_section_name(snode); + uint16_t index; + + // first check the address + if(!strcasestr(section, bdstr)) { + continue; + } + + if(btif_bap_get_section_index(section, &index)) { + index_status[index] = INDEX_OCCUPIED; + } + } + + // find the unused index + for(i = 0; i < MAX_INDEX; i++) { + if(index_status[i] == INDEX_FREE) break; + } + + if(i != MAX_INDEX) { + char index_str[MAX_INDEX_LEN]; + // form the section entry ( bd address plus index) + snprintf(index_str, sizeof(index_str), ":%02x", i); + strlcpy(section, bdstr, MAX_SECTION_LEN); + strlcat(section, index_str, MAX_SECTION_LEN); + return true; + } else { + return false; + } +} + +static bool btif_bap_find_sections(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record, + std::vector *sections) { + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char *section = config_section_name(snode); + // first check the address + if(!strcasestr(section, bdstr)) { + continue; + } + + // next check the record type + const char* value_str = config_get_string(config, section, + BAP_RECORD_TYPE_KEY, NULL); + if(value_str == nullptr || + ((rec_type == REC_TYPE_CAPABILITY && + strcasecmp(value_str, BAP_RECORD_TYPE_CAPA)) || + (rec_type == REC_TYPE_CONFIGURATION && + strcasecmp(value_str, BAP_RECORD_TYPE_CONF)))) { + continue; + } + + // next check the record type + uint16_t context = config_get_uint16(config, section, + BAP_CONTEXT_TYPE_KEY, 0XFFFF); + LOG(WARNING) << __func__ << ": context " << context + << ": context_type " << context_type; + if(context != context_type) { + continue; + } + + // next check the direction + value_str = config_get_string(config, section, + BAP_DIRECTION_KEY, NULL); + if(value_str == nullptr || + ((direction == CodecDirection::CODEC_DIR_SRC && + strcasecmp(value_str, "SRC")) || + (direction == CodecDirection::CODEC_DIR_SINK && + strcasecmp(value_str, "SINK")))) { + continue; + } + + if(record == nullptr) { + sections->push_back((char*) section); + } else { + // next check codec type + value_str = config_get_string(config, section, + BAP_CODEC_TYPE_KEY, NULL); + + if(value_str == nullptr || + (record->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3 && + strcasecmp(value_str, "LC3"))) { + continue; + } + + // next check the freqency + uint16_t value = config_get_uint16(config, section, + BAP_SAMP_FREQS_KEY, 0); + if(value == static_cast (record->sample_rate)) { + sections->push_back((char*) section); + } + } + } + + if(sections->size()) { + return true; + } else { + return false; + } +} + +static bool btif_bap_update_LC3_codec_info(char *section, CodecConfig *record, + btif_bap_record_type_t rec_type) { + if(section == nullptr || record == nullptr) { + return false; + } + + if(rec_type == REC_TYPE_CAPABILITY) { + + config_set_string(config, section, BAP_RECORD_TYPE_KEY, + BAP_RECORD_TYPE_CAPA); + + if(record->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + config_set_string(config, section, BAP_CODEC_TYPE_KEY, "LC3"); + } + // update freqs + config_set_uint16(config, section, BAP_SAMP_FREQS_KEY , + static_cast(record->sample_rate)); + + // update chnl count + config_set_uint16(config, section, BAP_CHNL_ALLOCATION_KEY, + static_cast (record->channel_mode)); + + // update supp frames + config_set_uint16(config, section, BAP_SUPP_FRM_DURATIONS_KEY , + static_cast (GetCapaSupFrameDurations(record))); + + // update chnl supp min octs per frame + config_set_uint16(config, section, BAP_SUP_MIN_OCTS_PER_FRAME_KEY , + static_cast (GetCapaSupOctsPerFrame(record) & + 0xFFFF)); + + // update chnl supp max octs per frame + config_set_uint16(config, section, BAP_SUP_MAX_OCTS_PER_FRAME_KEY, + static_cast ((GetCapaSupOctsPerFrame(record) & + 0xFFFF0000) >> 16)); + + // update max supp codec frames per sdu + config_set_uint16(config, section, BAP_MAX_SUP_CODEC_FRAMES_PER_SDU, + static_cast (GetCapaMaxSupLc3Frames(record))); + + // update LC3Q support + if (GetCapaVendorMetaDataLc3QPref(record)) { + config_set_string(config, section, BAP_LC3Q_SUP_KEY, "true"); + } else { + config_set_string(config, section, BAP_LC3Q_SUP_KEY, "false"); + } + + // update LC3Q Version + config_set_uint16(config, section, BAP_LC3Q_VER_KEY, + static_cast (GetCapaVendorMetaDataLc3QVer(record))); + } else { + + config_set_string(config, section, BAP_RECORD_TYPE_KEY, + BAP_RECORD_TYPE_CONF); + + if(record->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + config_set_string(config, section, BAP_CODEC_TYPE_KEY, "LC3"); + } + + // update freqs + config_set_uint16(config, section, BAP_SAMP_FREQS_KEY , + static_cast(record->sample_rate)); + + // update chnl count + config_set_uint16(config, section, BAP_CHNL_ALLOCATION_KEY, + static_cast (record->channel_mode)); + + // update configured frame duration + config_set_uint16(config, section, BAP_CONF_FRAME_DUR_KEY, + static_cast (GetFrameDuration(record))); + + // update configured octs per frame + config_set_uint16(config, section, BAP_CONF_OCTS_PER_FRAME_KEY, + static_cast (GetOctsPerFrame(record))); + + // update LC3 frames per SDU + config_set_uint16(config, section, BAP_LC3_FRAMES_PER_SDU_KEY, + static_cast (GetLc3BlocksPerSdu(record))); + } + + if (is_restricted_mode()) { + LOG(WARNING) << __func__ << ": records will be removed if unrestricted"; + config_set_uint16(config, section, "Restricted", 1); + } + + return true; +} + +bool btif_bap_add_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record) { + // first check if same record already exists + std::unique_lock lock(config_lock); + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, record, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + btif_bap_update_LC3_codec_info((*it), record , rec_type); + } + } else { + LOG(WARNING) << __func__ << ": section not found"; + char section[MAX_SECTION_LEN]; + btif_bap_get_free_section_id(bd_addr, section); + + config_set_uint16(config, section, BAP_CONTEXT_TYPE_KEY, context_type); + + if(direction == CodecDirection::CODEC_DIR_SRC) { + config_set_string(config, section, BAP_DIRECTION_KEY, "SRC"); + } else { + config_set_string(config, section, BAP_DIRECTION_KEY, "SINK"); + } + btif_bap_update_LC3_codec_info(section, record , rec_type); + } + btif_bap_config_save(); + return true; +} + +bool btif_bap_remove_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record) { + // first check if same record exists + // if exists remove the record by complete section + std::unique_lock lock(config_lock); + bool record_removed = false; + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, record, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + config_remove_section(config, (*it)); + } + record_removed = true; + btif_bap_config_flush(); + } + return record_removed; +} + +bool btif_bap_remove_record_by_context(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction) { + // first check if same record exists + // if exists remove the record by complete section + std::unique_lock lock(config_lock); + bool record_removed = false; + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, nullptr, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + config_remove_section(config, (*it)); + } + record_removed = true; + btif_bap_config_flush(); + } + return record_removed; +} + +bool btif_bap_remove_all_records(const RawAddress& bd_addr) { + // loop through the file if any record is found delete it + std::unique_lock lock(config_lock); + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + bool record_removed = false; + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char* section = config_section_name(snode); + // first check the address + if(strcasestr(section, bdstr)) { + record_removed = true; + config_remove_section(config, section); + } + } + btif_bap_config_flush(); + return record_removed; +} + +bool btif_bap_get_records(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + std::vector *pac_records) { + std::unique_lock lock(config_lock); + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, nullptr, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + CodecConfig record; + memset(&record, 0, sizeof(record)); + + if(config_has_key(config, (*it), BAP_SAMP_FREQS_KEY)) { + record.sample_rate = static_cast + (config_get_uint16(config, (*it), + BAP_SAMP_FREQS_KEY, + 0x00)); + } + + if (config_has_key(config, (*it), BAP_LC3Q_SUP_KEY)) { + bool lc3q_sup = false; + const char* is_lc3q_sup = config_get_string(config, (*it), + BAP_LC3Q_SUP_KEY, + ""); + if(!strcmp(is_lc3q_sup, "true")) { + lc3q_sup = true; + } + LOG(WARNING) << __func__ << ": lc3q_sup: " << lc3q_sup; + if (lc3q_sup) { + UpdateCapaVendorMetaDataLc3QPref(&record, lc3q_sup); + } + } + + if(config_has_key(config, (*it), BAP_LC3Q_VER_KEY)) { + uint16_t lc3q_ver = config_get_uint16(config, (*it), + BAP_LC3Q_VER_KEY, + 0x00); + LOG(WARNING) << __func__ << ": lc3q_ver: " << lc3q_ver; + UpdateCapaVendorMetaDataLc3QVer(&record, lc3q_ver); + } + + record.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + if(rec_type == REC_TYPE_CAPABILITY) { + + if(config_has_key(config, (*it), BAP_SUPP_FRM_DURATIONS_KEY)) { + uint16_t supp_frames = config_get_uint16(config, (*it), + BAP_SUPP_FRM_DURATIONS_KEY, + 0x00); + UpdateCapaSupFrameDurations(&record, supp_frames); + } + + // update chnl supp octs per frame + if(config_has_key(config, (*it), BAP_SUP_MIN_OCTS_PER_FRAME_KEY) && + config_has_key(config, (*it), BAP_SUP_MAX_OCTS_PER_FRAME_KEY)) { + uint16_t sup_min_octs = config_get_uint16(config, (*it), + BAP_SUP_MIN_OCTS_PER_FRAME_KEY, + 0x00); + uint16_t sup_max_octs = config_get_uint16(config, (*it), + BAP_SUP_MAX_OCTS_PER_FRAME_KEY, + 0x00); + UpdateCapaSupOctsPerFrame(&record, sup_min_octs | sup_max_octs << 16); + } + + // update max supp codec frames per sdu + if(config_has_key(config, (*it), BAP_MAX_SUP_CODEC_FRAMES_PER_SDU)) { + uint16_t max_sup_codec_frames_per_sdu = config_get_uint16(config, (*it), + BAP_MAX_SUP_CODEC_FRAMES_PER_SDU, + 0x00); + UpdateCapaMaxSupLc3Frames(&record, max_sup_codec_frames_per_sdu); + } + + // update preferred context type. + if(config_has_key(config, (*it), BAP_CONTEXT_TYPE_KEY)) { + uint16_t context_type = config_get_uint16(config, (*it), + BAP_CONTEXT_TYPE_KEY, + 0x00); + UpdateCapaPreferredContexts(&record, context_type); + } + } else { + + if(config_has_key(config, (*it), BAP_CONF_FRAME_DUR_KEY)) { + uint16_t conf_frames = config_get_uint16(config, (*it), + BAP_CONF_FRAME_DUR_KEY, + 0x00); + UpdateFrameDuration(&record, conf_frames); + } + + if(config_has_key(config, (*it), BAP_CONF_OCTS_PER_FRAME_KEY)) { + uint16_t conf_octs_per_frame = config_get_uint16(config, (*it), + BAP_CONF_OCTS_PER_FRAME_KEY, + 0x00); + UpdateOctsPerFrame(&record, conf_octs_per_frame); + } + + if(config_has_key(config, (*it), BAP_LC3_FRAMES_PER_SDU_KEY)) { + uint16_t lc3_frms_per_sdu = config_get_uint16(config, (*it), + BAP_LC3_FRAMES_PER_SDU_KEY, + 0x00); + UpdateLc3BlocksPerSdu(&record, lc3_frms_per_sdu); + } + } + pac_records->push_back(record); + } + } + + if(pac_records->size()) { + return true; + } else { + return false; + } +} + +bool btif_bap_add_audio_loc(const RawAddress& bd_addr, + CodecDirection direction, uint32_t audio_loc) { + // first check if same already exists + // if exists update the same entry + // audio location will always be stored @ 0th index + // form the section entry ( bd address plus index) + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + if(direction == CodecDirection::CODEC_DIR_SRC) { + config_set_int(config, section, BAP_SRC_LOCATIONS_KEY, audio_loc); + } else { + config_set_int(config, section, BAP_SINK_LOCATIONS_KEY, audio_loc); + } + btif_bap_config_save(); + return true; +} + +bool btif_bap_rem_audio_loc(const RawAddress& bd_addr, + CodecDirection direction) { + // first check if same record already exists + // if exists remove the record by complete section + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + if(direction == CodecDirection::CODEC_DIR_SRC) { + config_remove_key(config, section, "BAP_SRC_LOCATIONS_KEY"); + } else { + config_remove_key(config, section, "BAP_SINK_LOCATIONS_KEY"); + } + btif_bap_config_flush(); + return true; +} + +bool btif_bap_add_supp_contexts(const RawAddress& bd_addr, + uint32_t supp_contexts) { + // first check if same already exists + // if exists update the same entry + // supp contexts will always be stored @ 0th index + // form the section entry ( bd address plus index) + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + LOG(WARNING) << __func__ << " supp_contexts " << supp_contexts; + config_set_uint64(config, section, + BAP_SUP_AUDIO_CONTEXTS_KEY, supp_contexts); + btif_bap_config_save(); + return true; +} + +bool btif_bap_get_supp_contexts(const RawAddress& bd_addr, + uint32_t *supp_contexts) { + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + *supp_contexts = config_get_uint64(config, section, + BAP_SUP_AUDIO_CONTEXTS_KEY, 0); + return true; +} + +bool btif_bap_rem_supp_contexts(const RawAddress& bd_addr) { + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + config_remove_key(config, section, BAP_SUP_AUDIO_CONTEXTS_KEY); + btif_bap_config_flush(); + return true; +} diff --git a/le_audio/system/bt/btif/src/btif_bap_uclient.cc b/le_audio/system/bt/btif/src/btif_bap_uclient.cc new file mode 100644 index 0000000000000000000000000000000000000000..7a049a5253c6c1475c2ddc65e9c64bc47c0ad981 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_uclient.cc @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_bap_uclient_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "osi/include/thread.h" +#include "btif_bap_codec_utils.h" + +#include "osi/include/properties.h" + +extern void do_in_bta_thread(const base::Location& from_here, + const base::Closure& task); +#include +#include +#include +#include +#include +#include +#include +#include "btif_api.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::ucast::UcastClient; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; +using bluetooth::bap::ucast::StreamReconfig; + +namespace bluetooth { +namespace bap { +namespace ucast { + +class UcastClientInterfaceImpl; +static std::unique_ptr UcastClientInstance = nullptr; + +class UcastClientInterfaceImpl + : public UcastClientInterface, + public UcastClientCallbacks { + ~UcastClientInterfaceImpl() = default; + void Init(UcastClientCallbacks* client_callbacks) override { + if(is_initialized) { + LOG(WARNING) << __func__ << " Already initialized, return"; + return; + } + is_initialized = true; + callbacks = client_callbacks; + char value[PROPERTY_VALUE_MAX]; + if(property_get("persist.vendor.service.bt.bap.enable_ucast", value, "false") + && !strcmp(value, "true")) { + LOG(INFO) << __func__ << " Registering PACS UUID "; + btif_register_uuid_srvc_disc(Uuid::FromString("1850")); + btif_register_uuid_srvc_disc(Uuid::FromString("184E")); + } + do_in_bta_thread( FROM_HERE, Bind(&UcastClient::Initialize, this)); + } + + void OnStreamState(const RawAddress &address, + std::vector streams_state_info) override { + if(!is_initialized) return; + do_in_jni_thread(FROM_HERE, Bind(&UcastClientCallbacks::OnStreamState, + Unretained(callbacks), address, + streams_state_info)); + } + + void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) override { + if(!is_initialized) return; + do_in_jni_thread(FROM_HERE, Bind(&UcastClientCallbacks::OnStreamConfig, + Unretained(callbacks), + address, streams_config_info)); + } + + void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + if(!is_initialized) return; + do_in_jni_thread(FROM_HERE, + Bind(&UcastClientCallbacks::OnStreamAvailable, + Unretained(callbacks), + address, src_audio_contexts, + sink_audio_contexts)); + } + + void Reconfigure(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Reconfigure, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void Start(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Start, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void Connect(std::vector &address, bool is_direct, + std::vector &streams_info) override { + for (auto it = address.begin(); it != address.end(); it++) { + LOG(INFO) << __func__ << " " << (*it); + } + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Connect, + Unretained(UcastClient::Get()), + address, is_direct, streams_info)); + } + + void Disconnect(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Disconnect, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void Stop(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Stop, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void UpdateStream(const RawAddress& address, + std::vector &update_streams) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::UpdateStream, + Unretained(UcastClient::Get()), + address, update_streams)); + } + + void Cleanup() override { + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::CleanUp)); + is_initialized = false; + } + + private: + bool is_initialized = false;; + UcastClientCallbacks* callbacks; +}; + +UcastClientInterface* btif_bap_uclient_get_interface() { + if (!UcastClientInstance) + UcastClientInstance.reset(new UcastClientInterfaceImpl()); + return UcastClientInstance.get(); +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc b/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..ffa3745633d253e5846693118ee4c18637fe2dfc --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc @@ -0,0 +1,2971 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_bap_uclient_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "osi/include/thread.h" +#include "btif_bap_codec_utils.h" + +#include "osi/include/properties.h" + +extern void do_in_bta_thread(const base::Location& from_here, + const base::Closure& task); +#include +#include +#include +#include +#include +#include +#include +#include "btif_bap_config.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::ucast::UcastClient; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; +using bluetooth::bap::ucast::StreamReconfig; + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecPriority; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecFrameDuration; +using bluetooth::bap::pacs::CodecChannelMode; +using bluetooth::bap::ucast::CodecQosConfig; +using bluetooth::bap::ucast::CISConfig; +using bluetooth::bap::ucast::CONTENT_TYPE_MEDIA; +using bluetooth::bap::ucast::CONTENT_TYPE_CONVERSATIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_GAME; +using bluetooth::bap::ucast::CONTENT_TYPE_LIVE; +using bluetooth::bap::ucast::CONTENT_TYPE_UNSPECIFIED; +using bluetooth::bap::ucast::ASE_DIRECTION_SRC; +using bluetooth::bap::ucast::ASE_DIRECTION_SINK; +using bluetooth::bap::ucast::StreamReconfigType; +using bluetooth::bap::ucast::ASCSConfig; + +static thread_t *test_thread; +static UcastClientInterface* sUcastClientInterface = nullptr; +static RawAddress bap_bd_addr; + +extern bluetooth::bap::pacs::PacsClientInterface* btif_pacs_client_get_interface(); + +class UcastClientCallbacksImpl : public UcastClientCallbacks { + public: + ~UcastClientCallbacksImpl() = default; + void OnStreamState(const RawAddress &address, + std::vector streams_state_info) override { + for (auto it = streams_state_info.begin(); + it != streams_state_info.end(); it++) { + LOG(WARNING) << __func__ << " stream type " << (it->stream_type.type); + LOG(WARNING) << __func__ << " stream dir " << loghex(it->stream_type.direction); + LOG(WARNING) << __func__ << " stream state " << static_cast (it->stream_state); + } + } + void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) override { + LOG(WARNING) << __func__; + for (auto it = streams_config_info.begin(); + it != streams_config_info.end(); it++) { + LOG(WARNING) << __func__ << " stream type " << (it->stream_type.type); + LOG(WARNING) << __func__ << " stream dir " << loghex(it->stream_type.direction); + LOG(WARNING) << __func__ << " location " << static_cast (it->audio_location); + btif_bap_add_record(address, REC_TYPE_CONFIGURATION, + it->stream_type.type, + static_cast (it->stream_type.direction), + &it->codec_config); + + std::vector acm_pac_records; + + btif_bap_get_records(address, + REC_TYPE_CAPABILITY, + CONTENT_TYPE_MEDIA | + CONTENT_TYPE_UNSPECIFIED, + static_cast + (it->stream_type.direction), + &acm_pac_records); + + LOG(WARNING) << __func__ << " acm len to be 3" << (acm_pac_records.size()); + + std::vector config_pac_records; + btif_bap_get_records(address, + REC_TYPE_CONFIGURATION, + CONTENT_TYPE_MEDIA, + static_cast + (it->stream_type.direction), + &config_pac_records); + + LOG(WARNING) << __func__ << " configs len to be 1" << (config_pac_records.size()); + + btif_bap_remove_record_by_context (address, REC_TYPE_CONFIGURATION, + it->stream_type.type, + static_cast + (it->stream_type.direction)); + + config_pac_records.clear(); + btif_bap_get_records(address, + REC_TYPE_CONFIGURATION, + CONTENT_TYPE_MEDIA, + static_cast + (it->stream_type.direction), + &config_pac_records); + + LOG(WARNING) << __func__ << " configs len to be 0" << (config_pac_records.size()); + +#if 0 + btif_bap_remove_record_by_context (address, REC_TYPE_CAPABILITY, + CONTENT_TYPE_MEDIA | + CONTENT_TYPE_UNSPECIFIED, + static_cast + (it->stream_type.direction)); + + acm_pac_records.clear(); + btif_bap_get_records(address, + REC_TYPE_CAPABILITY, + CONTENT_TYPE_MEDIA | + CONTENT_TYPE_UNSPECIFIED, + static_cast + (it->stream_type.direction), + &acm_pac_records); + + LOG(WARNING) << __func__ << " acm len to be 0" << (acm_pac_records.size()); +#endif + } + } + void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + LOG(WARNING) << __func__; + } +}; + +static UcastClientCallbacksImpl sUcastClientCallbacks; + +static void event_test_bap_uclient(UNUSED_ATTR void* context) { + LOG(INFO) << __func__ << " start " ; + sUcastClientInterface = bluetooth::bap::ucast::btif_bap_uclient_get_interface(); + sUcastClientInterface->Init(&sUcastClientCallbacks); + sleep(1); + + StreamConnect conn_info; + CodecQosConfig codec_qos_config; + codec_qos_config.codec_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config.codec_config, 100); + codec_qos_config.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + CISConfig cis_config_1 = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2 = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + codec_qos_config.qos_config.cis_configs.push_back(cis_config_1); + codec_qos_config.qos_config.cis_configs.push_back(cis_config_2); + + ASCSConfig ascs_config = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config); + conn_info.stream_type.type = 0x0004; // media + conn_info.stream_type.direction = ASE_DIRECTION_SINK; + conn_info.stream_type.audio_context = + CONTENT_TYPE_MEDIA; + conn_info.codec_qos_config_pair.push_back(codec_qos_config); + std::vector streams; + streams.push_back(conn_info); + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config.qos_config.ascs_configs.clear(); + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config_2); + StreamConnect conn_info_2; + conn_info_2.stream_type.type = 0x0004; // media + conn_info_2.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_2.stream_type.audio_context = + CONTENT_TYPE_MEDIA; + conn_info_2.codec_qos_config_pair.push_back(codec_qos_config); + std::vector streams_2; + streams_2.push_back(conn_info_2); + + // reconfig information + std::vector reconf_streams; + codec_qos_config.qos_config.ascs_configs[0].cis_id = 1; + UpdateFrameDuration(&codec_qos_config.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + + codec_qos_config.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x4C, 0x1D, 0x00}, + .sdu_interval_s_to_m = {0x4C, 0x1D, 0x00} + }; + + StreamReconfig reconf_info; + reconf_info.stream_type.type = 0x0004; // media + reconf_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_info.codec_qos_config_pair.push_back(codec_qos_config); + reconf_streams.push_back(reconf_info); + + std::vector reconf_qos_streams; + StreamReconfig reconf_qos_info; + reconf_qos_info.stream_type.type = 0x0004; // media + reconf_qos_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_qos_info.stream_type.direction = ASE_DIRECTION_SINK; + codec_qos_config.qos_config.cis_configs.clear(); + + cis_config_1.rtn_m_to_s = 1; + cis_config_1.rtn_s_to_m = 1; + cis_config_2.rtn_m_to_s = 1; + cis_config_2.rtn_s_to_m = 1; + codec_qos_config.qos_config.cis_configs.push_back(cis_config_1); + codec_qos_config.qos_config.cis_configs.push_back(cis_config_2); + reconf_qos_info.codec_qos_config_pair.push_back(codec_qos_config); + reconf_qos_streams.push_back(reconf_qos_info); + + StreamType type_1 = { .type = 0x0004, + .direction = ASE_DIRECTION_SINK + }; + + RawAddress bap_bd_addr_2; + RawAddress::FromString("00:02:5B:00:FF:01", bap_bd_addr_2); + + std::vector start_streams; + start_streams.push_back(type_1); + + char bap_test[150] = "generic"; + + property_get("persist.vendor.service.bt.bap.test", bap_test, "generic"); + + LOG(INFO) << __func__ << " property" << bap_test; + + if(!strcmp(bap_test, "generic")) { + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, streams_2); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start 1 "; + //sUcastClientInterface->Start( bap_bd_addr, start_streams); + + LOG(INFO) << __func__ << " going for stream start 2"; + //sUcastClientInterface->Start( bap_bd_addr_2, start_streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + LOG(INFO) << __func__ << "going for stream disconnect 2 "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, start_streams); + + } else if(!strcmp(bap_test, "stereo_recording_stereo_dual_cis")) { + LOG(INFO) << __func__ << " going for rxonly test case"; + LOG(INFO) << __func__ << " going for connect"; + StreamConnect conn_info_media_recording; + + CodecQosConfig codec_qos_config_media_rx_1; + CodecQosConfig codec_qos_config_media_rx_2; + + CISConfig cis_config_1_media_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_media_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_media_rx_2 = { + .cis_id = 0, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 200, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + ASCSConfig ascs_config_1 = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_rx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_rx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_rx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_rx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_rx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_rx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_rx_1.codec_config, 100); + + + codec_qos_config_media_rx_2 = codec_qos_config_media_rx_1; + UpdateOctsPerFrame(&codec_qos_config_media_rx_2.codec_config, 200); + + codec_qos_config_media_rx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_1_media_rx); + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_2_media_rx); + + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_1); + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_media_rx_2.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 1, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_rx_2.qos_config.cis_configs.push_back(cis_config_1_media_rx_2); + + codec_qos_config_media_rx_2.qos_config.ascs_configs.push_back(ascs_config_1); + + StreamType type_media_rx = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + + conn_info_media_recording.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media_recording.stream_type.audio_context = CONTENT_TYPE_LIVE; + conn_info_media_recording.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_media_recording.codec_qos_config_pair.push_back(codec_qos_config_media_rx_1); + conn_info_media_recording.codec_qos_config_pair.push_back(codec_qos_config_media_rx_2); + + std::vector media_rx_streams; + media_rx_streams.push_back(conn_info_media_recording); + + std::vector streams; + streams.push_back(type_media_rx); + + + sUcastClientInterface->Connect( bap_bd_addr, true, media_rx_streams); + + sleep(15); + + LOG(INFO) << __func__ << " going for stream start 1 "; + sUcastClientInterface->Start( bap_bd_addr, streams); + + sleep(10); + + sUcastClientInterface->Stop( bap_bd_addr, streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, streams); + + } else if(!strcmp(bap_test, "dual_streams_media_tx_voice_rx")) { + StreamConnect conn_info_media; + StreamConnect conn_info_gaming; + + // reconfig information + CodecQosConfig codec_qos_config_media_tx_1; + CodecQosConfig codec_qos_config_gaming_tx; + CodecQosConfig codec_qos_config_gaming_tx_rx; + + CISConfig cis_config_1_media_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_media_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_gaming_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_gaming_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_gaming_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2_gaming_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + ASCSConfig ascs_config = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_tx_1.codec_config, 155); + codec_qos_config_media_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_1_media_tx); + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_2_media_tx); + + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config); + codec_qos_config_gaming_tx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx.codec_config, 100); + codec_qos_config_gaming_tx.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx); + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx); + + codec_qos_config_gaming_tx.qos_config.ascs_configs.push_back(ascs_config); + codec_qos_config_gaming_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_gaming_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_rx.codec_config, 40); + codec_qos_config_gaming_tx_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + ASCSConfig ascs_config_gaming = { + .cig_id = 2, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + codec_qos_config_gaming_tx_rx.qos_config.ascs_configs.push_back(ascs_config_gaming); + conn_info_media.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + conn_info_gaming.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_gaming.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_rx); + + std::vector dual_streams; + dual_streams.push_back(conn_info_media); + dual_streams.push_back(conn_info_gaming); + + StreamType type_media = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + + std::vector media_streams; + media_streams.push_back(type_media); + std::vector voice_streams; + voice_streams.push_back(type_voice); + + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect with media tx and gaming rx"; + sUcastClientInterface->Connect( bap_bd_addr, true, dual_streams); + + //sUcastClientInterface->Connect( bap_bd_addr_2, true, streams_2); + + sleep(10); + + std::vector reconf_streams; + + StreamReconfig reconf_gaming_tx_info; + reconf_gaming_tx_info.stream_type.type = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx); + + StreamReconfig reconf_gaming_tx_rx_info; + reconf_gaming_tx_rx_info.stream_type.type = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_rx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_rx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_rx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_rx); + + StreamReconfig reconf_media_info; + reconf_media_info.stream_type.type = CONTENT_TYPE_MEDIA; // media + reconf_media_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; // media + reconf_media_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + sleep(5); + LOG(INFO) << __func__ << " going for stream start 1 "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + // switch to Gaming tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + // switch to media tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + // switch to Gaming tx & rx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + + // switch to Gaming tx (2nd time) (4th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_gaming_tx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + + // switch to Gaming tx & rx (2nd time) (5th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + + // switch to media tx (6th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, media_streams); + sUcastClientInterface->Disconnect( bap_bd_addr, voice_streams); + //LOG(INFO) << __func__ << "going for stream disconnect 2 "; + //sUcastClientInterface->Disconnect( bap_bd_addr_2, start_streams); + + } else if(!strcmp(bap_test, "tri_streams_media_tx_voice_tx_voice_rx")) { + StreamConnect conn_info_media; + StreamConnect conn_info_gaming; + StreamConnect conn_info_voice_tx; + StreamConnect conn_info_voice_rx; + + // reconfig information + CodecQosConfig codec_qos_config_media_tx_1; + CodecQosConfig codec_qos_config_gaming_tx; + CodecQosConfig codec_qos_config_gaming_tx_rx; + CodecQosConfig codec_qos_config_gaming_rx; + CodecQosConfig codec_qos_config_voice_tx_rx; + + CodecQosConfig codec_qos_config_media_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_rx_2; + CodecQosConfig codec_qos_config_gaming_rx_2; + CodecQosConfig codec_qos_config_voice_tx_rx_2; + + CISConfig cis_config_1_media_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_media_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_gaming_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_gaming_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_gaming_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + CISConfig cis_config_2_gaming_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_voice_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 40, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + CISConfig cis_config_2_voice_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 40, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + ASCSConfig ascs_config = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming = { + .cig_id = 2, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_voice = { + .cig_id = 3, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming_2 = { + .cig_id = 2, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_voice_2 = { + .cig_id = 3, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_tx_1.codec_config, 155); + codec_qos_config_media_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_1_media_tx); + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_2_media_tx); + + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config); + + codec_qos_config_media_tx_2 = codec_qos_config_media_tx_1; + codec_qos_config_media_tx_2.qos_config.ascs_configs.clear(); + codec_qos_config_media_tx_2.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_gaming_tx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx.codec_config, 100); + codec_qos_config_gaming_tx.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx); + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx); + + codec_qos_config_gaming_tx.qos_config.ascs_configs.push_back(ascs_config); + + + codec_qos_config_gaming_tx_2 = codec_qos_config_gaming_tx; + codec_qos_config_gaming_tx_2.qos_config.ascs_configs.clear(); + codec_qos_config_gaming_tx_2.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_gaming_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_rx.codec_config, 100); + codec_qos_config_gaming_tx_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + + codec_qos_config_gaming_tx_rx.qos_config.ascs_configs.push_back(ascs_config_gaming); + + codec_qos_config_gaming_tx_rx_2 = codec_qos_config_gaming_tx_rx; + codec_qos_config_gaming_tx_rx_2.qos_config.ascs_configs.clear(); + codec_qos_config_gaming_tx_rx_2.qos_config.ascs_configs.push_back(ascs_config_gaming_2); + + + codec_qos_config_gaming_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_gaming_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_rx.codec_config, 40); + codec_qos_config_gaming_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + + codec_qos_config_gaming_rx.qos_config.ascs_configs.push_back(ascs_config_gaming); + + codec_qos_config_gaming_rx_2 = codec_qos_config_gaming_rx; + codec_qos_config_gaming_rx_2.qos_config.ascs_configs.clear(); + codec_qos_config_gaming_rx_2.qos_config.ascs_configs.push_back(ascs_config_gaming_2); + + + // voice tx rx + codec_qos_config_voice_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_voice_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_voice_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_voice_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_voice_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_voice_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_voice_tx_rx.codec_config, 40); + codec_qos_config_voice_tx_rx.qos_config.cig_config = { + .cig_id = 3, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_1_voice_tx_rx); + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_2_voice_tx_rx); + + codec_qos_config_voice_tx_rx.qos_config.ascs_configs.push_back(ascs_config_voice); + + codec_qos_config_voice_tx_rx_2 = codec_qos_config_voice_tx_rx; + codec_qos_config_voice_tx_rx_2.qos_config.ascs_configs.clear(); + codec_qos_config_voice_tx_rx_2.qos_config.ascs_configs.push_back(ascs_config_voice_2); + + conn_info_media.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + conn_info_gaming.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_gaming.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_rx); + + conn_info_voice_tx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_voice_tx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + conn_info_voice_rx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_voice_rx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + std::vector tri_streams; + tri_streams.push_back(conn_info_media); + tri_streams.push_back(conn_info_voice_tx); + tri_streams.push_back(conn_info_voice_rx); + + conn_info_media.codec_qos_config_pair.clear(); + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + conn_info_voice_tx.codec_qos_config_pair.clear(); + conn_info_voice_tx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx_2); + + conn_info_voice_rx.codec_qos_config_pair.clear(); + conn_info_voice_rx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx_2); + + std::vector tri_streams_2; + tri_streams_2.push_back(conn_info_media); + tri_streams_2.push_back(conn_info_voice_tx); + tri_streams_2.push_back(conn_info_voice_rx); + + StreamType type_media = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_tx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_rx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + + + std::vector media_streams; + media_streams.push_back(type_media); + + std::vector gaming_tx_streams; + gaming_tx_streams.push_back(type_media); + + std::vector gaming_rx_streams; + gaming_rx_streams.push_back(type_voice_rx); + + std::vector voice_streams; + voice_streams.push_back(type_voice_tx); + voice_streams.push_back(type_voice_rx); + + std::vector all_three_streams; + all_three_streams.push_back(type_media); + all_three_streams.push_back(type_voice_tx); + all_three_streams.push_back(type_voice_rx); + +#if 0 + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect with media tx and voice tx& rx"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + sleep(15); + + LOG(INFO) << __func__ << "going for stream disconnect all 3 streams "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for connect all 3 streams"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + + sleep(15); + + LOG(INFO) << __func__ << "going for stream disconnect all 3 streams "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); +#endif + + LOG(INFO) << __func__ << " going for connect all 3 streams"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + + std::vector reconf_streams; + std::vector reconf_streams_2; + + StreamReconfig reconf_gaming_tx_info; + reconf_gaming_tx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx); + + StreamReconfig reconf_gaming_tx_info_2; + reconf_gaming_tx_info_2.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info_2.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx_2); + + StreamReconfig reconf_gaming_tx_rx_info; + reconf_gaming_tx_rx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_rx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx_rx); + + StreamReconfig reconf_gaming_tx_rx_info_2; + reconf_gaming_tx_rx_info_2.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info_2.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_rx_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_rx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx_rx_2); + + StreamReconfig reconf_gaming_rx_info; + reconf_gaming_rx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_gaming_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_gaming_rx); + + StreamReconfig reconf_gaming_rx_info_2; + reconf_gaming_rx_info_2.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info_2.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_rx_info_2.stream_type.direction = ASE_DIRECTION_SRC; + reconf_gaming_rx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_gaming_rx_2); + + StreamReconfig reconf_media_info; + reconf_media_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_media_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + StreamReconfig reconf_media_info_2; + reconf_media_info_2.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_info_2.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_media_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info_2.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + + StreamReconfig reconf_voice_tx_info; + reconf_voice_tx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_voice_tx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_voice_tx_info_2; + reconf_voice_tx_info_2.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info_2.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_tx_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_voice_tx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx_2); + + StreamReconfig reconf_voice_rx_info; + reconf_voice_rx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_voice_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_voice_rx_info_2; + reconf_voice_rx_info_2.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info_2.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_rx_info_2.stream_type.direction = ASE_DIRECTION_SRC; + reconf_voice_rx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx_2); + + sleep(15); + LOG(INFO) << __func__ << " going for stream start 1 "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to Gaming tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_gaming_tx_info_2); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to media tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_media_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to Gaming tx & rx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_rx_info); + reconf_streams.push_back(reconf_gaming_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_gaming_tx_rx_info_2); + reconf_streams_2.push_back(reconf_gaming_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_rx_streams); + + // switch to Gaming tx (2nd time) (4th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_rx_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_gaming_tx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_info_2.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_info_2); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to Gaming tx & rx (2nd time) (5th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_rx_info); + reconf_streams.push_back(reconf_gaming_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_rx_info_2.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_rx_info_2); + reconf_streams_2.push_back(reconf_gaming_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_rx_streams); + + // switch to media tx (6th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_rx_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_media_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to voice tx & Rx (7th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + // switch to gaming tx (8th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_info_2); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + + // switch to voice tx & Rx (9th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + // switch to Gaming tx & rx (10th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_rx_info); + reconf_streams.push_back(reconf_gaming_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_rx_info_2); + reconf_streams_2.push_back(reconf_gaming_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_rx_streams); + + + // switch to voice tx & Rx (11th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_rx_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + // switch to media tx (12th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_media_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to voice tx & Rx (13th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for connect all 3 streams"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + + sleep(15); + LOG(INFO) << __func__ << "going for stream disconnect all 3 streams "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); + + } else if(!strcmp(bap_test, "stereo_headset_dual_cis")) { + StreamConnect conn_info_media; + StreamConnect conn_info_gaming; + StreamConnect conn_info_voice_tx; + StreamConnect conn_info_voice_rx; + StreamConnect conn_info_media_recording; + + // reconfig information + CodecQosConfig codec_qos_config_media_tx_1; + CodecQosConfig codec_qos_config_media_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_1; + CodecQosConfig codec_qos_config_gaming_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_rx; + CodecQosConfig codec_qos_config_voice_tx_rx; + CodecQosConfig codec_qos_config_voice_tx_rx_2; + CodecQosConfig codec_qos_config_media_rx_1; + CodecQosConfig codec_qos_config_media_rx_2; + + CISConfig cis_config_1_media_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_media_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_media_tx_2 = { + .cis_id = 0, + .max_sdu_m_to_s = 310, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + + CISConfig cis_config_1_gaming_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_gaming_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_gaming_tx_2 = { + .cis_id = 0, + .max_sdu_m_to_s = 200, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + + CISConfig cis_config_1_gaming_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2_gaming_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_voice_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 80, + .max_sdu_s_to_m = 80, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2_voice_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 80, + .max_sdu_s_to_m = 80, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_media_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_media_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + ASCSConfig ascs_config_1 = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming_1 = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_voice_1 = { + .cig_id = 3, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x40, 0x9C, 0x00} + }; + + ASCSConfig ascs_config_voice_2 = { + .cig_id = 3, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x40, 0x9C, 0x00} + }; + + ASCSConfig ascs_config_media_rx_1 = { + .cig_id = 4, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_media_rx_2 = { + .cig_id = 4, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_tx_1.codec_config, 155); + + codec_qos_config_media_tx_2 = codec_qos_config_media_tx_1; + UpdateOctsPerFrame(&codec_qos_config_media_tx_2.codec_config, 310); + + codec_qos_config_media_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_1_media_tx); + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_2_media_tx); + + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config_1); + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_media_tx_2.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 1, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_2.qos_config.cis_configs.push_back(cis_config_1_media_tx_2); + + codec_qos_config_media_tx_2.qos_config.ascs_configs.push_back(ascs_config_1); + + codec_qos_config_gaming_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_1.codec_config, 100); + + codec_qos_config_gaming_tx_2 = codec_qos_config_gaming_tx_1; + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_2.codec_config, 200); + + codec_qos_config_gaming_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_1.qos_config.cis_configs.push_back(cis_config_1_gaming_tx); + codec_qos_config_gaming_tx_1.qos_config.cis_configs.push_back(cis_config_2_gaming_tx); + + codec_qos_config_gaming_tx_1.qos_config.ascs_configs.push_back(ascs_config_gaming_1); + codec_qos_config_gaming_tx_1.qos_config.ascs_configs.push_back(ascs_config_gaming_2); + + codec_qos_config_gaming_tx_2.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 1, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_2.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_2); + + codec_qos_config_gaming_tx_2.qos_config.ascs_configs.push_back(ascs_config_gaming_1); + + codec_qos_config_gaming_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_gaming_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_rx.codec_config, 40); + codec_qos_config_gaming_tx_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + + codec_qos_config_gaming_tx_rx.qos_config.ascs_configs.push_back(ascs_config_gaming_1); + + // voice tx rx + codec_qos_config_voice_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_voice_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_voice_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_32000; + codec_qos_config_voice_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_voice_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_voice_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_voice_tx_rx.codec_config, 80); + codec_qos_config_voice_tx_rx.qos_config.cig_config = { + .cig_id = 3, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_1_voice_tx_rx); + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_2_voice_tx_rx); + + codec_qos_config_voice_tx_rx.qos_config.ascs_configs.push_back(ascs_config_voice_1); + codec_qos_config_voice_tx_rx.qos_config.ascs_configs.push_back(ascs_config_voice_2); + + + codec_qos_config_media_rx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_rx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_rx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_rx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_rx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_rx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_rx_1.codec_config, 100); + + codec_qos_config_media_rx_1.qos_config.cig_config = { + .cig_id = 4, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_1_media_rx); + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_2_media_rx); + + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_media_rx_1); + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_media_rx_2); + + conn_info_media.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + conn_info_gaming.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.audio_context = CONTENT_TYPE_GAME; + conn_info_gaming.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_gaming.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_1); + + conn_info_voice_tx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_voice_tx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + conn_info_voice_rx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_voice_rx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + conn_info_media_recording.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media_recording.stream_type.audio_context = CONTENT_TYPE_LIVE; + conn_info_media_recording.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_media_recording.codec_qos_config_pair.push_back(codec_qos_config_media_rx_1); + + std::vector four_streams; + four_streams.push_back(conn_info_media); + four_streams.push_back(conn_info_media_recording); + four_streams.push_back(conn_info_voice_tx); + four_streams.push_back(conn_info_voice_rx); + + std::vector voice_conn_streams; + voice_conn_streams.push_back(conn_info_voice_tx); + voice_conn_streams.push_back(conn_info_voice_rx); + + std::vector media_conn_streams; + media_conn_streams.push_back(conn_info_media); + + StreamType type_media = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_gaming_tx = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_GAME, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_tx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_rx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + + StreamType type_media_rx = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + + std::vector media_streams; + media_streams.push_back(type_media); + + std::vector gaming_tx_streams; + gaming_tx_streams.push_back(type_gaming_tx); + + std::vector media_rx_streams; + media_rx_streams.push_back(type_media_rx); + + std::vector voice_streams; + voice_streams.push_back(type_voice_tx); + voice_streams.push_back(type_voice_rx); + + std::vector all_four_streams; + all_four_streams.push_back(type_media); + all_four_streams.push_back(type_voice_tx); + all_four_streams.push_back(type_voice_rx); + all_four_streams.push_back(type_media_rx); + + std::vector reconf_streams; + + StreamReconfig reconf_media_info; + reconf_media_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_media_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + StreamReconfig reconf_gaming_tx_info; + reconf_gaming_tx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_1); + reconf_gaming_tx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_2); + + StreamReconfig reconf_voice_tx_info; + reconf_voice_tx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_voice_tx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_voice_rx_info; + reconf_voice_rx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_voice_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_media_rx_info; + reconf_media_rx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_rx_info.stream_type.audio_context = CONTENT_TYPE_LIVE; + reconf_media_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_media_rx_info.codec_qos_config_pair.push_back(codec_qos_config_media_rx_1); + + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + +#if 0 + // Recording to music switch (12) + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + sleep(5); + + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + sleep(15); + + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(1); + + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + +#endif +#if 1 + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + //usleep(200000); + + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + sleep(5); + //usleep(200000); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); +#endif + + sleep(15); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + + // Music streaming + LOG(INFO) << __func__ << "going for media stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // music to gaming Tx switch (1) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + + // Gaming Tx to music switch (2) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // music to Call Tx Rx switch (3) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + + + // Gaming to Cal Audio switch (5) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + + sleep(5); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + + // Cal Audio to music switch (6) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + + // music to Recording switch (7) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_rx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for recording stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_rx_streams); + + + // Recording to Gaming switch (8) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_rx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + + + // Gaming to Recording switch (9) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_rx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for recording stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_rx_streams); + + + // Recording to Call Audio switch (10) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_rx_streams); + + sleep(5); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Audio to Recording switch (11) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_rx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for recording stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_rx_streams); + + + // Recording to music switch (12) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_rx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + } else if(!strcmp(bap_test, "connect")) { + LOG(INFO) << __func__ << " going for connect test case"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + usleep( i* 1000 * 1000); + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + } else if(!strcmp(bap_test, "release_in_streaming")) { + LOG(INFO) << __func__ << " going for release_in_streaming test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + + } else if(!strcmp(bap_test, "release_in_enabling")) { + LOG(INFO) << __func__ << " going for release_in_enabling test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + usleep(150 *1000); + + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + + } else if(!strcmp(bap_test, "release_in_disabling")) { + LOG(INFO) << __func__ << " going for release_in_disabling test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + + usleep(100 * 1000); + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "disable_in_enabling")) { + LOG(INFO) << __func__ << " going for disable_in_enabling test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + usleep(200 * 1000); + LOG(INFO) << __func__ << "going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "disable_in_streaming")) { + LOG(INFO) << __func__ << " going for disable_in_streaming test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "release_in_codec_configured")) { + LOG(INFO) << __func__ << " going for release_in_codec_configured test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + usleep(2600 * 1000); + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "release_in_qos_configured")) { + LOG(INFO) << __func__ << " going for release_in_qos_configured test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "enable_in_qos_configured")) { + LOG(INFO) << __func__ << " going for enable_in_qos_configured test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "start")) { + LOG(INFO) << __func__ << " going for start test case"; + + LOG(INFO) << __func__ << " going for stop while starting test case"; + + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + sleep(5); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + + LOG(INFO) << __func__ << " going for disconnect while starting test case"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + sleep(5); + } + + } else if(!strcmp(bap_test, "stop")) { + LOG(INFO) << __func__ << " going for stop test case"; + LOG(INFO) << __func__ << " going for connect"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for disconnect while stopping"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + + } else if(!strcmp(bap_test, "stop")) { + LOG(INFO) << __func__ << " going for stop test case"; + LOG(INFO) << __func__ << " going for connect"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for disconnect while stopping"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + } else if(!strcmp(bap_test, "reconfigure")) { + LOG(INFO) << __func__ << " going for reconfigure test case"; + LOG(INFO) << __func__ << " going for connect"; + + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream qos reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_qos_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } + + LOG(INFO) << __func__ << " Test completed"; +#if 0 + sleep(2); + LOG(INFO) << __func__ << "going for connect"; + sUcastClientInterface->Connect( bap_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery"; + sUcastClientInterface->StartDiscovery( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts"; + sUcastClientInterface->GetAvailableAudioContexts( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr); + sleep(1); + sUcastClientInterface->Cleanup(); + sleep(1); + sUcastClientInterface->Init(&sUcastClientCallbacks); + sleep(1); + LOG(INFO) << __func__ << "going for connect 2 "; + sUcastClientInterface->Connect( bap_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery 2"; + sUcastClientInterface->StartDiscovery( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts 2"; + sUcastClientInterface->GetAvailableAudioContexts( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect 2"; + sUcastClientInterface->Disconnect( bap_bd_addr); + sleep(1); + sUcastClientInterface->Cleanup(); +#endif +} + +bool test_bap_uclient () { + RawAddress::FromString("00:02:5b:00:ff:00", bap_bd_addr); + test_thread = thread_new("test_bap_uclient"); + LOG(INFO) << __func__ << "going for test setup"; + thread_post(test_thread, event_test_bap_uclient, NULL); + return true; +} + +// PACS related test code +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::pacs::PacsClientCallbacks; + +static PacsClientInterface* sPacsClientInterface = nullptr; +static uint16_t pacs_client_id = 0; +static RawAddress pac_bd_addr; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, + int client_id) override { + LOG(WARNING) << __func__ << client_id; + pacs_client_id = client_id; + } + void OnConnectionState(const RawAddress& bd_addr, + ConnectionState state) override { + LOG(WARNING) << __func__; + if(state == ConnectionState::CONNECTED) { + LOG(WARNING) << __func__ << "connected"; + } else if(state == ConnectionState::DISCONNECTED) { + LOG(WARNING) << __func__ << "Disconnected"; + } + + } + void OnAudioContextAvailable(const RawAddress& bd_addr, + uint32_t available_contexts) override { + LOG(INFO) << __func__; + } + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + LOG(WARNING) << __func__; + } +}; + +static PacsClientCallbacksImpl sPacsClientCallbacks; + +static void event_test_pacs(UNUSED_ATTR void* context) { + sPacsClientInterface = btif_pacs_client_get_interface(); + sPacsClientInterface->Init(&sPacsClientCallbacks); + sleep(1); + LOG(INFO) << __func__ << "going for connect"; + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery"; + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts"; + sPacsClientInterface->GetAvailableAudioContexts(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect"; + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); + + sleep(2); + LOG(INFO) << __func__ << "going for connect"; + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery"; + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts"; + sPacsClientInterface->GetAvailableAudioContexts(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect"; + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); + + sleep(1); + sPacsClientInterface->Cleanup(pacs_client_id); + + sleep(1); + + sPacsClientInterface->Init(&sPacsClientCallbacks); + sleep(1); + LOG(INFO) << __func__ << "going for connect 2 "; + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery 2"; + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts 2"; + sPacsClientInterface->GetAvailableAudioContexts(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect 2"; + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); + + sleep(1); + sPacsClientInterface->Cleanup(pacs_client_id); + +} + +bool test_pacs (RawAddress& bd_addr) { + + test_thread = thread_new("test_pacs"); + pac_bd_addr = bd_addr; + thread_post(test_thread, event_test_pacs, NULL); + return true; +} diff --git a/le_audio/system/bt/btif/src/btif_cc.cc b/le_audio/system/bt/btif/src/btif_cc.cc new file mode 100644 index 0000000000000000000000000000000000000000..59c355da1b56884e92c2e5e5b491af3cf21a3efa --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_cc.cc @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* CC Interface */ +#define LOG_TAG "bt_btif_cc" + +#include "bt_target.h" +#include "bta_closure_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "bta_cc_api.h" + +#include +#include +#include +#include +#include +#include +#include + +using base::Bind; +using base::Unretained; +using base::Owned; +using bluetooth::Uuid; +using std::vector; + +using base::Unretained; +using bluetooth::call_control::CallControllerInterface; +using bluetooth::call_control::CallControllerCallbacks; + +namespace { +class CallControllerInterfaceImpl; +std::unique_ptr CallControllerInstance; + +class CallControllerInterfaceImpl + : public CallControllerInterface, public CallControllerCallbacks { + ~CallControllerInterfaceImpl() = default; + + bt_status_t Init(CallControllerCallbacks* callbacks, Uuid uuid, int max_ccs_clients, + bool inband_ringing_enabled) override { + + LOG(INFO) << __func__ ; + this->callbacks = callbacks; + do_in_bta_thread(FROM_HERE,Bind(&CallController::Initialize, + this, uuid,max_ccs_clients, inband_ringing_enabled)); + return BT_STATUS_SUCCESS; + } + + bt_status_t UpdateBearerName(uint8_t* operator_str) { + + LOG(INFO) << __func__ << ": bearer name " << operator_str; + uint8_t* bName = (uint8_t*)malloc(sizeof(uint8_t)*strlen((char*)operator_str)+1); + if (bName != NULL) { + memcpy(bName, operator_str, strlen((char*)operator_str)+1); + do_in_bta_thread(FROM_HERE,Bind(&CallController::BearerInfoName, + Unretained(CallController::Get()), bName)); + free(bName); + } + return BT_STATUS_SUCCESS; + } + + void Cleanup() { + LOG(INFO) << __func__ ; + do_in_bta_thread(FROM_HERE,Bind(&CallController::CleanUp)); + } + +bt_status_t UpdateBearerTechnology(int bearer_tech) { + + LOG(INFO) << __func__ << ": " << bearer_tech; + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateBearerTechnology, + Unretained(CallController::Get()), bearer_tech)); + return BT_STATUS_SUCCESS; +} + +bt_status_t UpdateSupportedBearerList(uint8_t* supportedbearer_list) { + + LOG(INFO) << __func__ << ": " << supportedbearer_list; + uint8_t* sList = (uint8_t*)malloc(sizeof(uint8_t)*strlen((char*)supportedbearer_list)+1); + if (sList != NULL) { + memcpy(sList, supportedbearer_list, strlen((char*)supportedbearer_list)+1); + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateSupportedBearerList, + Unretained(CallController::Get()), sList)); + free(sList); + } + return BT_STATUS_SUCCESS; + +} +bt_status_t UpdateStatusFlags(uint8_t status_flag) { + LOG(INFO) << __func__ << ": " << status_flag; + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateStatusFlags, + Unretained(CallController::Get()), status_flag)); + return BT_STATUS_SUCCESS; +} + +bt_status_t UpdateSignalStatus(int signal) { + + LOG(INFO) << __func__ << ": " << signal; + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateBearerSignalStrength, + Unretained(CallController::Get()), signal)); + return BT_STATUS_SUCCESS; + +} + +bt_status_t CallControlOptionalOpSupported(int feature) { + + LOG(INFO) << __func__ << ": " << feature; + do_in_bta_thread(FROM_HERE,Bind(&CallController::CallControlOptionalOpSupported, + Unretained(CallController::Get()), feature)); + return BT_STATUS_SUCCESS; +} + +bt_status_t CallState(int len, std::vector call_state_list) { + + LOG(INFO) << __func__ << ": "; + do_in_bta_thread(FROM_HERE,Bind(&CallController::CallState, + Unretained(CallController::Get()), len, std::move(call_state_list))); + return BT_STATUS_SUCCESS; + +} + +void UpdateIncomingCall(int index, uint8_t* Uri) { + LOG(INFO) << __func__ << ": " < +#include +#include +#include +#include +#include +#include +#include +#include +#include "device/include/controller.h" +#include "bta_csip_api.h" + +#include "btif_api.h" +#include "btif_common.h" +#include "btif_util.h" + +#include + +using base::Bind; +using bluetooth::Uuid; + +static btcsip_callbacks_t* bt_csip_callbacks = NULL; + +void btif_new_set_found_cb(tBTA_CSIP_NEW_SET_FOUND params) { + HAL_CBACK(bt_csip_callbacks, new_set_found_cb, params.set_id, params.addr, + params.size, params.sirk, params.including_srvc_uuid, + params.lock_support); +} + +void btif_conn_state_changed_cb(tBTA_CSIP_CONN_STATE_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, conn_state_cb, params.app_id, params.addr, + params.state, params.status); +} + +void btif_new_set_member_found_cb(tBTA_SET_MEMBER_FOUND params) { + HAL_CBACK(bt_csip_callbacks, new_set_member_cb, params.set_id, params.addr); +} + +void btif_lock_status_changed_cb(tBTA_LOCK_STATUS_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, lock_status_cb, params.app_id, params.set_id, + params.value, params.status, params.addr); +} + +void btif_lock_available_cb(tBTA_LOCK_AVAILABLE params) { + HAL_CBACK(bt_csip_callbacks, lock_available_cb, params.app_id, params.set_id, + params.addr); +} + +void btif_set_size_changed_cb (tBTA_CSIP_SET_SIZE_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, size_changed_cb, params.set_id, params.size, + params.addr); +} + +void btif_set_sirk_changed_cb (tBTA_CSIP_SET_SIRK_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, sirk_changed_cb, params.set_id, params.sirk, + params.addr); +} + +const char* btif_csip_get_event_name(tBTA_CSIP_EVT event) { + switch(event) { + case BTA_CSIP_LOCK_STATUS_CHANGED_EVT: + return "BTA_CSIP_LOCK_STATUS_CHANGED_EVT"; + case BTA_CSIP_SET_MEMBER_FOUND_EVT: + return "BTA_CSIP_SET_MEMBER_FOUND_EVT"; + case BTA_CSIP_LOCK_AVAILABLE_EVT: + return "BTA_CSIP_LOCK_AVAILABLE_EVT"; + case BTA_CSIP_NEW_SET_FOUND_EVT: + return "BTA_CSIP_NEW_SET_FOUND_EVT"; + case BTA_CSIP_CONN_STATE_CHG_EVT: + return "BTA_CSIP_CONN_STATE_CHG_EVT"; + case BTA_CSIP_SET_SIZE_CHANGED: + return "BTA_CSIP_SET_SIZE_CHANGED"; + case BTA_CSIP_SET_SIRK_CHANGED: + return "BTA_CSIP_SET_SIRK_CHANGED"; + default: + return "UNKNOWN_EVENT"; + } +} + +void btif_csip_evt (tBTA_CSIP_EVT event, tBTA_CSIP_DATA* p_data) { + BTIF_TRACE_EVENT("%s: Event = %02x (%s)", __func__, event, btif_csip_get_event_name(event)); + + switch (event) { + case BTA_CSIP_LOCK_STATUS_CHANGED_EVT: { + tBTA_LOCK_STATUS_CHANGED lock_status_params = p_data->lock_status_param; + do_in_jni_thread(Bind(btif_lock_status_changed_cb, lock_status_params)); + } + break; + + case BTA_CSIP_LOCK_AVAILABLE_EVT: { + tBTA_LOCK_AVAILABLE lock_avl_param = p_data->lock_available_param; + do_in_jni_thread(Bind(btif_lock_available_cb, lock_avl_param)); + } + break; + + case BTA_CSIP_NEW_SET_FOUND_EVT: { + tBTA_CSIP_NEW_SET_FOUND new_set_params = p_data->new_set_params; + memcpy(new_set_params.sirk, p_data->new_set_params.sirk, SIRK_SIZE); + do_in_jni_thread(Bind(btif_new_set_found_cb, new_set_params)); + } + break; + + case BTA_CSIP_SET_MEMBER_FOUND_EVT: { + tBTA_SET_MEMBER_FOUND new_member_params = p_data->set_member_param; + do_in_jni_thread(Bind(btif_new_set_member_found_cb, new_member_params)); + } + break; + + case BTA_CSIP_CONN_STATE_CHG_EVT: { + tBTA_CSIP_CONN_STATE_CHANGED conn_params = p_data->conn_params; + do_in_jni_thread(Bind(btif_conn_state_changed_cb, conn_params)); + } + break; + + case BTA_CSIP_SET_SIZE_CHANGED: { + tBTA_CSIP_SET_SIZE_CHANGED size_chg_param = p_data->size_chg_params; + do_in_jni_thread(Bind(btif_set_size_changed_cb, size_chg_param)); + } + break; + + case BTA_CSIP_SET_SIRK_CHANGED: { + tBTA_CSIP_SET_SIRK_CHANGED sirk_chg_param = p_data->sirk_chg_params; + do_in_jni_thread(Bind(btif_set_sirk_changed_cb, sirk_chg_param)); + } + break; + + default: + BTIF_TRACE_ERROR("%s: Unknown event %d", __func__, event); + } +} + +/* Initialization of CSIP module on BT ON*/ +bt_status_t btif_csip_init( btcsip_callbacks_t* callbacks ) { + bt_csip_callbacks = callbacks; + + do_in_jni_thread(Bind(BTA_CsipEnable, btif_csip_evt)); + btif_register_uuid_srvc_disc(Uuid::FromString("1846")); + + return BT_STATUS_SUCCESS; +} + +/* Connect call from upper layer for GATT Connecttion to a given Set Member */ +bt_status_t btif_csip_connect (uint8_t app_id, RawAddress *bd_addr) { + BTIF_TRACE_EVENT("%s: Address: %s", __func__, bd_addr->ToString().c_str()); + + do_in_jni_thread(Bind(BTA_CsipConnect, app_id, *bd_addr)); + + return BT_STATUS_SUCCESS; +} + +/* Call from upper layer to disconnect GATT Connection for given Set Member */ +bt_status_t btif_csip_disconnect (uint8_t app_id, RawAddress *bd_addr ) { + BTIF_TRACE_EVENT("%s", __func__); + + do_in_jni_thread(Bind(BTA_CsipDisconnect, app_id, *bd_addr)); + + return BT_STATUS_SUCCESS; +} + +/** register app/module with CSIP profile */ +bt_status_t btif_csip_app_register (const bluetooth::Uuid& uuid) { + BTIF_TRACE_EVENT("%s", __func__); + return do_in_jni_thread(Bind( + [](const Uuid& uuid) { + BTA_RegisterCsipApp( + btif_csip_evt, + base::Bind( + [](const Uuid& uuid, uint8_t status, uint8_t app_id) { + do_in_jni_thread(Bind( + [](const Uuid& uuid, uint8_t status, uint8_t app_id) { + HAL_CBACK(bt_csip_callbacks, app_registered_cb, + status, app_id, uuid); + }, + uuid, status, app_id)); + }, + uuid)); + }, uuid)); +} + +/** unregister csip App/Module */ +bt_status_t btif_csip_app_unregister (uint8_t app_id) { + BTIF_TRACE_EVENT("%s", __func__); + return do_in_jni_thread(Bind(BTA_UnregisterCsipApp, app_id)); +} + +/** change lock value */ +bt_status_t btif_csip_set_lock_value (uint8_t app_id, uint8_t set_id, uint8_t lock_value, + std::vector devices) { + BTIF_TRACE_EVENT("%s appId = %d setId = %d Lock Value = %02x ", __func__, + app_id, set_id, lock_value); + tBTA_SET_LOCK_PARAMS lock_params = {app_id, set_id, lock_value, devices}; + do_in_jni_thread(Bind(BTA_CsipSetLockValue, lock_params)); + return BT_STATUS_SUCCESS; +} + +void btif_csip_cleanup() { + BTIF_TRACE_EVENT("%s", __func__); + do_in_jni_thread(Bind(BTA_CsipDisable)); +} + +const btcsip_interface_t btcsipInterface = { + sizeof(btcsipInterface), + btif_csip_init, + btif_csip_connect, + btif_csip_disconnect, + btif_csip_app_register, + btif_csip_app_unregister, + btif_csip_set_lock_value, + btif_csip_cleanup, +}; + +/******************************************************************************* + * + * Function btif_csip_get_interface + * + * Description Get the csip callback interface + * + * Returns btcsip_interface_t + * + ******************************************************************************/ +const btcsip_interface_t* btif_csip_get_interface() { + BTIF_TRACE_EVENT("%s", __func__); + return &btcsipInterface; +} diff --git a/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc b/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..a9c32f0ffd25ee447aff16c955a6d7415d124057 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc @@ -0,0 +1,692 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#define LOG_TAG "bt_btif_dm" + +#include "btif_dm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "hardware/vendor.h" + +#include +#include + +#include "advertise_data_parser.h" +#include "bt_common.h" +#include "bta_closure_api.h" +#include "bta_csip_api.h" +#include "bta_gatt_api.h" +#include "btif_api.h" +#include "btif_bqr.h" +#include "btif_config.h" +#include "btif_dm.h" +#include "btif_hh.h" +#include "btif_sdp.h" +#include "btif_storage.h" +#include "btif_util.h" +#include "btu.h" +#include "bta/include/bta_dm_api.h" +#include "device/include/controller.h" +#include "device/include/interop.h" +#include "internal_include/stack_config.h" +#include "osi/include/allocator.h" +#include "osi/include/log.h" +#include "osi/include/metrics.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "stack/btm/btm_int.h" +#include "stack_config.h" +#include "stack/sdp/sdpint.h" +#include "btif_tws_plus.h" +#include "device/include/device_iot_config.h" +#include "btif_bap_config.h" +#include "bta_dm_adv_audio.h" +#include "btif_dm_adv_audio.h" + +using bluetooth::Uuid; + +/****************************************************************************** + * Constants & Macros + *****************************************************************************/ +#define BTIF_DM_GET_REMOTE_PROP(b,t,v,l,p) \ + {p.type=t;p.val=v;p.len=l;btif_storage_get_remote_device_property(b,&p);} + +extern std::vector uuid_srv_disc_search; +std::unordered_map adv_audio_device_db; +extern void bta_dm_adv_audio_gatt_conn(RawAddress p_bd_addr); +extern void bta_dm_adv_audio_close(RawAddress p_bd_addr); +extern bool btif_has_ble_keys(const char* bdstr); +bt_status_t btif_storage_get_remote_device_property( + const RawAddress* remote_bd_addr, bt_property_t* property); +extern tBTA_LEA_PAIRING_DB bta_lea_pairing_cb; +extern void search_services_copy_cb(uint16_t event, char* p_dest, char* p_src); + +extern void bond_state_changed(bt_status_t status, const RawAddress& bd_addr, + bt_bond_state_t state); + +#define BTIF_STORAGE_GET_REMOTE_PROP(b, t, v, l, p) \ + do { \ + (p).type = (t); \ + (p).val = (v); \ + (p).len = (l); \ + btif_storage_get_remote_device_property((b), &(p)); \ + } while (0) + +extern bool check_adv_audio_cod(uint32_t cod); +extern bool is_remote_support_adv_audio(const RawAddress remote_bdaddr); +extern bool is_le_audio_service(Uuid uuid); +extern void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport); + +#define BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING 2 + + +tBTA_TRANSPORT btif_dm_get_adv_audio_transport(const RawAddress& bd_addr) { + tBTM_INQ_INFO* p_inq_info; + + p_inq_info = BTM_InqDbRead(bd_addr); + if (p_inq_info != NULL) { + BTIF_TRACE_DEBUG("%s, inq_result_type %x", + __func__, p_inq_info->results.inq_result_type); + if (p_inq_info->results.inq_result_type & BTM_INQ_RESULT_BLE) { + return BT_TRANSPORT_LE; + } + } + return BT_TRANSPORT_BR_EDR; +} + +/******************************************************************************* + * + * Function btif_set_remote_device_uuid_property + * + * Description Store the remote LEA services in config file + * + * Returns void + * + ******************************************************************************/ +static void btif_set_remote_device_uuid_property(RawAddress p_addr, + int num_uuids, + bluetooth::Uuid *new_uuids) { + + Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + bt_property_t prop; + + for (int j = 0; j < num_uuids; j++) { + remote_uuids[j] = new_uuids[j]; + BTIF_TRACE_EVENT("%s: UUID %s index %d ", __func__, + remote_uuids[j].ToString().c_str(), j); + } + prop.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + prop.val = &remote_uuids[0]; + prop.len = (num_uuids) * (Uuid::kNumBytes128); + Uuid* tmp = (Uuid*)(prop.val); + BTIF_TRACE_EVENT("%s: Checking it %s", __func__, tmp->ToString().c_str()); + int ret = btif_storage_set_remote_device_property(&p_addr, &prop); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", + ret); +} + +/******************************************************************************* + * + * Function btif_dm_lea_search_services_evt + * + * Description Executes search services event in btif context + * + * Returns void + * + ******************************************************************************/ +void btif_dm_lea_search_services_evt(uint16_t event, char* p_param) { + tBTA_DM_SEARCH* p_data = (tBTA_DM_SEARCH*)p_param; + + bt_bond_state_t pairing_state = BT_BOND_STATE_NONE; + uint8_t sdp_attempts = 0; + RawAddress pairing_bd_addr; + RawAddress static_bd_addr; + btif_get_pairing_cb_info(&pairing_state, &sdp_attempts, + &pairing_bd_addr, &static_bd_addr); + + BTIF_TRACE_EVENT("%s: event = %d", __func__, event); + switch (event) { + case BTA_DM_DISC_RES_EVT: { + uint32_t i = 0, j = 0; + bt_property_t prop[2]; + int num_properties = 0; + bt_status_t ret; + Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + Uuid missed_remote_uuids[BT_MAX_NUM_UUIDS]; + uint8_t missing_uuids_len = 0; + bt_property_t remote_uuid_prop; + + RawAddress& bd_addr = p_data->disc_res.bd_addr; + + BTIF_TRACE_DEBUG("%s:(result=0x%x, services 0x%x)", __func__, + p_data->disc_res.result, p_data->disc_res.services); + + /* retry sdp service search, if sdp fails for pairing bd address, + ** report sdp results to APP immediately for non pairing addresses + */ + if ((p_data->disc_res.result != BTA_SUCCESS) && + (pairing_state == BT_BOND_STATE_BONDED) && + ((p_data->disc_res.bd_addr == pairing_bd_addr) || + (p_data->disc_res.bd_addr == static_bd_addr)) && + (sdp_attempts > 0)) { + if (sdp_attempts < BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING) { + BTIF_TRACE_WARNING("%s:SDP failed after bonding re-attempting", + + __func__); + btif_inc_sdp_attempts(); + btif_dm_get_remote_services_by_transport(&bd_addr, BT_TRANSPORT_BR_EDR); + return; + } else { + BTIF_TRACE_WARNING( + "%s: SDP reached to maximum attempts, sending bond fail to upper layers", + __func__); + btif_reset_sdp_attempts(); + if (bta_remote_device_is_dumo(bd_addr)) { + auto itr = bta_lea_pairing_cb.dev_addr_map.find(bd_addr); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + if ((itr->first != itr->second)) { + bta_lea_pairing_cb.is_sdp_discover = false; + bond_state_changed(BT_STATUS_FAIL, + bd_addr, BT_BOND_STATE_NONE); + } else { + btif_reset_pairing_cb(); + BTIF_TRACE_WARNING("%s: Skipping BOND_NONE for %s", __func__, + bd_addr.ToString().c_str()); + } + } else { + BTIF_TRACE_ERROR("%s: SDP shouldnt on random address. Wrong path %s", __func__, + bd_addr.ToString().c_str()); + btif_reset_pairing_cb(); + bond_state_changed(BT_STATUS_FAIL, + bd_addr, BT_BOND_STATE_NONE); + btif_storage_remove_bonded_device(&bd_addr); + BTA_DmRemoveDevice(bd_addr); + } + return; + } else { + BTIF_TRACE_ERROR("%s: SDP shouldnt called. Wrong path %s", __func__, + bd_addr.ToString().c_str()); + } + } + } + prop[0].type = BT_PROPERTY_UUIDS; + prop[0].len = 0; + if ((p_data->disc_res.result == BTA_SUCCESS) && + (p_data->disc_res.num_uuids > 0)) { + prop[0].val = p_data->disc_res.p_uuid_list; + prop[0].len = p_data->disc_res.num_uuids * Uuid::kNumBytes128; + + for (i = 0; i < p_data->disc_res.num_uuids; i++) { + std::string temp = ((p_data->disc_res.p_uuid_list + i))->ToString(); + LOG_INFO(LOG_TAG, "%s index:%d uuid:%s", __func__, i, temp.c_str()); + } + } + + /* onUuidChanged requires getBondedDevices to be populated. + ** bond_state_changed needs to be sent prior to remote_device_property + */ + if ((pairing_state == BT_BOND_STATE_BONDED && sdp_attempts) && + (p_data->disc_res.bd_addr == pairing_bd_addr || + p_data->disc_res.bd_addr == static_bd_addr)) { + LOG_INFO(LOG_TAG, "%s: SDP search done for %s", __func__, + bd_addr.ToString().c_str()); + btif_reset_sdp_attempts(); + BTA_DmResetPairingflag(bd_addr); + btif_reset_pairing_cb(); + + // Send one empty UUID to Java to unblock pairing intent when SDP failed + // or no UUID is discovered + if (p_data->disc_res.result != BTA_SUCCESS || + p_data->disc_res.num_uuids == 0) { + LOG_INFO(LOG_TAG, + "%s: SDP failed, send empty UUID to unblock bonding %s", + __func__, bd_addr.ToString().c_str()); + bt_property_t prop; + + Uuid uuid = {}; + //Updating in lea_pairing_database + if (bta_remote_device_is_dumo(bd_addr)) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + + p_lea_pair_cb = bta_get_lea_pair_cb(bd_addr); + if (p_lea_pair_cb) { + p_lea_pair_cb->sdp_disc_status = false; + } + } + + if (btif_dm_get_adv_audio_transport(bd_addr) == BT_TRANSPORT_BR_EDR) + { + prop.type = BT_PROPERTY_UUIDS; + } else { + prop.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + } + prop.val = &uuid; + prop.len = Uuid::kNumBytes128; + + /* Send the event to the BTIF */ + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, + BT_STATUS_SUCCESS, &bd_addr, 1, &prop); + break; + } + } + + // updates extra uuids which are discovered during + // new sdp search to existing uuid list present in conf file. + // If conf file has more UUIDs than the sdp search, it will + // update the conf file UUIDs as the final UUIDs + BTIF_STORAGE_FILL_PROPERTY(&remote_uuid_prop, BT_PROPERTY_UUIDS, + sizeof(remote_uuids), remote_uuids); + btif_storage_get_remote_device_property(&bd_addr, + &remote_uuid_prop); + if(remote_uuid_prop.len && p_data->disc_res.result == BTA_SUCCESS) { + // compare now + bool uuid_found = false; + uint8_t uuid_len = remote_uuid_prop.len / sizeof(Uuid); + for (i = 0; i < p_data->disc_res.num_uuids; i++) { + uuid_found = false; + Uuid* disc_uuid = reinterpret_cast (p_data->disc_res.p_uuid_list + i); + for (j = 0; j < uuid_len; j++) { + Uuid* base_uuid = reinterpret_cast (remote_uuid_prop.val) + j; + if(*disc_uuid == *base_uuid) { + uuid_found = true; + break; + } + } + if(!uuid_found) { + BTIF_TRACE_WARNING("%s:new uuid found ", __func__); + memcpy(&missed_remote_uuids[missing_uuids_len++], disc_uuid, sizeof(Uuid)); + } + } + + // add the missing uuids now + if(missing_uuids_len) { + BTIF_TRACE_WARNING("%s :missing_uuids_len = %d ", __func__, missing_uuids_len); + for (j = 0; j < missing_uuids_len && + (unsigned long)remote_uuid_prop.len < BT_MAX_NUM_UUIDS * sizeof(Uuid); j++) { + memcpy(&remote_uuids[uuid_len + j], &missed_remote_uuids[j], sizeof(Uuid)); + remote_uuid_prop.len += sizeof(Uuid); + } + } + + prop[0].type = BT_PROPERTY_UUIDS; + prop[0].val = remote_uuids; + prop[0].len = remote_uuid_prop.len; + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[0]); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", + ret); + //Send the UUID values to upper layer as BT_PROPERTY_ADV_AUDIO_UUIDS + num_properties++; + + if (bta_is_bredr_primary_transport(bd_addr)) { + BTIF_TRACE_WARNING("%s: Initiating LE connection ", __func__); + adv_audio_device_db[bd_addr] = MAJOR_LE_AUDIO_VENDOR_COD; + bta_le_audio_dev_cb.bond_progress = true; + bta_dm_adv_audio_gatt_conn(bd_addr); + } + } else if (p_data->disc_res.num_uuids != 0) { + /* Also write this to the NVRAM */ + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[0]); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", + ret); + num_properties++; + } + + /* Remote name update */ + if (strlen((const char *) p_data->disc_res.bd_name)) { + prop[1].type = BT_PROPERTY_BDNAME; + prop[1].val = p_data->disc_res.bd_name; + prop[1].len = strlen((char *)p_data->disc_res.bd_name); + + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[1]); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device property", ret); + num_properties++; + } + + if (num_properties > 0) { + if (btif_dm_get_adv_audio_transport(bd_addr) == BT_TRANSPORT_BR_EDR) + { + prop[0].type = BT_PROPERTY_UUIDS; + } else { + prop[0].type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + } + /* Send the event to the BTIF */ + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, num_properties, prop); + } + + int validAddr = 1; + bt_property_t rem_prop; + BTIF_STORAGE_GET_REMOTE_PROP(&bd_addr, (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR, + &validAddr, sizeof(int), + rem_prop); + + if (validAddr != 0) { + bt_property_t prop_addr; + int is_valid = bta_is_adv_audio_valid_bdaddr(bd_addr); + prop_addr.type = (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR; + prop_addr.val = (void *)&is_valid; + prop_addr.len = sizeof(int); + ret = btif_storage_set_remote_device_property(&bd_addr, &prop_addr); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device property", ret); + } + + bt_device_type_t dev_type; + dev_type = (bt_device_type_t)BT_DEVICE_TYPE_DUMO; + bt_property_t prop_dev; + BTIF_STORAGE_FILL_PROPERTY(&prop_dev, + BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type), + &dev_type); + ret = btif_storage_set_remote_device_property(&bd_addr, &prop_dev); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device type", + ret); + + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop_dev); + + /* If below condition is true, it means LE random advertising + * has no ADV audio uuids, but identity address contains adv audio bit + * As per current design, if pairing initiated through non adv audio + * address then we dont need to fetch ADV audio role and services + */ + if ((btif_get_is_adv_audio_pair_info(bd_addr) == 0)) { + prop_dev.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_ACTION_UUID; + prop_dev.val = (void *)&validAddr; + prop_dev.len = sizeof(uint8_t); + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop_dev); + } + + } break; + + case BTA_DM_DISC_CMPL_EVT: + /* fixme */ + break; + + case BTA_DM_SEARCH_CANCEL_CMPL_EVT: + /* no-op */ + break; + + case BTA_DM_DISC_BLE_RES_EVT: { + BTIF_TRACE_DEBUG("%s: service %s", __func__, + p_data->disc_ble_res.service.ToString().c_str()); + bt_property_t prop; + bt_status_t ret; + RawAddress& bd_addr = p_data->disc_ble_res.bd_addr; + /* Remote name update */ + if (strnlen((const char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN)) { + prop.type = BT_PROPERTY_BDNAME; + prop.val = p_data->disc_ble_res.bd_name; + prop.len = + strnlen((char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN); + + ret = btif_storage_set_remote_device_property(&bd_addr, &prop); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + /* Send the event to the BTIF */ + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop); + } + } break; + + case BTA_DM_LE_AUDIO_SEARCH_CMPL_EVT: + { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_data->disc_ble_res.bd_addr); + if (p_lea_pair_cb != NULL) { + btif_reset_pairing_cb(); + bt_property_t prop[5], prop_tmp[2]; + RawAddress& bd_addr = p_data->disc_ble_res.bd_addr; + int num_properties = 0; + bool id_addr_action_uuid = false; + RawAddress id_addr = bta_get_rem_dev_id_addr(bd_addr); + + prop[num_properties].type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + prop[num_properties].val = p_data->adv_audio_disc_cmpl.adv_audio_uuids; + prop[num_properties].len = p_data->adv_audio_disc_cmpl.num_uuids * + Uuid::kNumBytes128; + /* Also write this to the NVRAM */ + bt_property_t cod_prop1; + uint32_t cod_p; + + BTIF_STORAGE_FILL_PROPERTY(&cod_prop1, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod_p), &cod_p); + btif_storage_get_remote_device_property(&bd_addr, + &cod_prop1); + int ret; + cod_p |= MAJOR_LE_AUDIO_VENDOR_COD; + BTIF_STORAGE_FILL_PROPERTY(&cod_prop1, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod_p), &cod_p); + ret = btif_storage_set_remote_device_property(&bd_addr, &cod_prop1); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + + if (bta_remote_device_is_dumo(bd_addr) + && (id_addr != bd_addr) && (bta_lea_pairing_cb.is_sdp_discover == true)) { + if(id_addr != RawAddress::kEmpty) { + BTIF_TRACE_DEBUG("%s: Found BT_PROPERTY_ADV_AUDIO_UUIDS %s", + p_data->adv_audio_disc_cmpl.adv_audio_uuids[0].ToString().c_str(), + id_addr.ToString().c_str()); + + bt_property_t cod_prop; + uint32_t cod; + + BTIF_STORAGE_FILL_PROPERTY(&cod_prop, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod), &cod); + btif_storage_get_remote_device_property(&id_addr, + &cod_prop); + BTIF_TRACE_DEBUG("%s: Cod is %x", __func__, cod); + cod |= MAJOR_LE_AUDIO_VENDOR_COD; + BTIF_STORAGE_FILL_PROPERTY(&cod_prop, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod), &cod); + ret = btif_storage_set_remote_device_property(&id_addr, &cod_prop); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + num_properties++; + prop[num_properties].type = BT_PROPERTY_CLASS_OF_DEVICE; + prop[num_properties].val = (void *) &cod; + prop[num_properties].len = sizeof(cod); + + num_properties ++; + + BTIF_STORAGE_FILL_PROPERTY(&prop[num_properties], + (bt_property_type_t)BT_PROPERTY_REM_DEV_IDENT_BD_ADDR, sizeof(RawAddress), &bd_addr); + ret = btif_storage_set_remote_device_property(&id_addr, &prop[num_properties]); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + + id_addr_action_uuid = true; + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &id_addr, 3, &prop[0]); + } + } + num_properties = 1; + + BTIF_STORAGE_FILL_PROPERTY(&prop[num_properties], + (bt_property_type_t)BT_PROPERTY_REM_DEV_IDENT_BD_ADDR, sizeof(RawAddress), &id_addr); + + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[num_properties]); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 2, &prop[0]); + + int validAddr = 1; + bt_property_t rem_prop; + BTIF_STORAGE_GET_REMOTE_PROP(&bd_addr, (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR, + &validAddr, sizeof(int), + rem_prop); + validAddr = bta_is_adv_audio_valid_bdaddr(bd_addr); + BTIF_TRACE_DEBUG("%s: is Valid Address Check value %d bd_addr %s", __func__, validAddr, bd_addr.ToString().c_str()); + prop_tmp[0].type = (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR; + prop_tmp[0].val = (void *)&validAddr; + prop_tmp[0].len = sizeof(int); + ret = btif_storage_set_remote_device_property(&bd_addr, &prop_tmp[0]); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device property", ret); + + prop_tmp[1].type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_ACTION_UUID; + prop_tmp[1].val = (void *)&validAddr; + prop_tmp[1].len = sizeof(uint8_t); + + if (id_addr_action_uuid) { + BTIF_TRACE_DEBUG("%s: IDENTITY ADDR ACTION UUID %s ", __func__, + id_addr.ToString().c_str()); + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &id_addr, 1, &prop_tmp[1]); + } + + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 2, &prop_tmp[0]); + if (id_addr_action_uuid) { + btif_set_remote_device_uuid_property(id_addr, + p_data->adv_audio_disc_cmpl.num_uuids, &p_data->adv_audio_disc_cmpl.adv_audio_uuids[0]); + } + btif_set_remote_device_uuid_property(bd_addr, + p_data->adv_audio_disc_cmpl.num_uuids, &p_data->adv_audio_disc_cmpl.adv_audio_uuids[0]); + + bta_dm_adv_audio_close(bd_addr); + bta_dm_reset_lea_pairing_info(bd_addr); + } else { + BTIF_TRACE_DEBUG("%s: ONCE AGAIN WRITING IDENTITY", __func__); + } + } + break; + default: { ASSERTC(0, "unhandled search services event", event); } break; + } +} + +/**************************************************************************** + * + * Function btif_register_uuid_srvc_disc + * + * Description Add to UUID to the service search queue + * + * Returns void + * + ****************************************************************************/ +void btif_register_uuid_srvc_disc(bluetooth::Uuid uuid) { + + uuid_srv_disc_search.push_back(uuid); + BTIF_TRACE_DEBUG("btif_register_uuid_srvc_disc, no of uuids %d %s", + uuid_srv_disc_search.size(), uuid.ToString().c_str()); +} + +void btif_dm_release_action_uuid(RawAddress bd_addr) { + + bt_property_t prop_dev; + int status = 1; + prop_dev.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_ACTION_UUID; + prop_dev.val = (void *)&status; + prop_dev.len = sizeof(uint8_t); + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop_dev); +} + +/**************************************************************************** + * + * Function check_adv_audio_cod + * + * Description This API is used to check whether COD contains LE Audio + * COD or not? + * + * Returns bool + * + ****************************************************************************/ +bool check_adv_audio_cod(uint32_t cod) { + + BTIF_TRACE_DEBUG("check_adv_audio_cod "); + + if (cod & MAJOR_LE_AUDIO_VENDOR_COD) { + return true; + } + return false; +} + +/******************************************************************************* + * + * Function is_remote_support_adv_audio + * + * Description is remote device is supporting LE audio or not + * + * Returns bool + * + ******************************************************************************/ + +bool is_remote_support_adv_audio(const RawAddress p_addr) { + if (adv_audio_device_db.find(p_addr) + != adv_audio_device_db.end()) { + BTIF_TRACE_DEBUG("%s %s LE AUDIO Support ", __func__, + p_addr.ToString().c_str()); + return true; + } + + bool status = bta_is_remote_support_lea(p_addr); + if (status) return true; + + bt_property_t cod_prop; + uint32_t cod_p; + + BTIF_STORAGE_FILL_PROPERTY(&cod_prop, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod_p), &cod_p); + btif_storage_get_remote_device_property(&p_addr, + &cod_prop); + + if ((cod_p & MAJOR_LE_AUDIO_VENDOR_COD) + == MAJOR_LE_AUDIO_VENDOR_COD) { + BTIF_TRACE_DEBUG("%s ADV AUDIO COD is matched ", __func__); + return true; + } + + return false; +} + +void bte_dm_adv_audio_search_services_evt(tBTA_DM_SEARCH_EVT event, + tBTA_DM_SEARCH* p_data) { + BTIF_TRACE_DEBUG(" %s ", __func__); + uint16_t param_len = 0; + if (p_data) param_len += sizeof(tBTA_DM_SEARCH); + switch (event) { + case BTA_DM_DISC_RES_EVT: { + if ((p_data && p_data->disc_res.result == BTA_SUCCESS) && + (p_data->disc_res.num_uuids > 0)) { + param_len += (p_data->disc_res.num_uuids * Uuid::kNumBytes128); + } + } break; + } + /* TODO: The only other member that needs a deep copy is the p_raw_data. But + * * not sure + * * if raw_data is needed. */ + btif_transfer_context( + btif_dm_lea_search_services_evt, event, (char*)p_data, param_len, + (param_len > sizeof(tBTA_DM_SEARCH)) ? search_services_copy_cb : NULL); +} + diff --git a/le_audio/system/bt/btif/src/btif_mcp.cc b/le_audio/system/bt/btif/src/btif_mcp.cc new file mode 100644 index 0000000000000000000000000000000000000000..35b4ee952911a65a90c032842f9368dc15e9c2d3 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_mcp.cc @@ -0,0 +1,185 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +/* MCP Interface */ +#define LOG_TAG "bt_btif_mcp" + +#include "bt_target.h" +#include "bta_closure_api.h" +#include "bta_mcp_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include + +using base::Bind; +using base::Unretained; +using base::Owned; +using bluetooth::Uuid; +using std::vector; +using base::Bind; +using base::Unretained; + +using bluetooth::mcp_server::McpServerCallbacks; +using bluetooth::mcp_server::McpServerInterface; + +namespace { +class McpServerInterfaceImpl; +std::unique_ptr McpServerInstance; + +class McpServerInterfaceImpl + : public McpServerInterface, public McpServerCallbacks { + ~McpServerInterfaceImpl() = default; + + void Init(McpServerCallbacks* callback, Uuid bt_uuid) override { + LOG(INFO) << __func__ ; + this->callbacks = callback; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::Initialize, this, bt_uuid)); + } + + void MediaState(uint8_t state) override { + LOG(INFO) << __func__ << ": state " << state; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaState, Unretained(McpServer::Get()), state)); + } + + void MediaPlayerName(uint8_t* name) override { + LOG(INFO) << __func__ << ": name" << name; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaPlayerName, Unretained(McpServer::Get()), name)); + } + + void MediaControlPointOpcodeSupported(uint32_t feature) override { + LOG(INFO) << __func__ << ": feature" << feature; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaControlPointOpcodeSupported, Unretained(McpServer::Get()), feature)); + } + + void MediaControlPoint(uint8_t value) override { + LOG(INFO) << __func__ << ": value" << value; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaControlPoint, Unretained(McpServer::Get()), value)); + } + + void TrackChanged(bool status) override { + LOG(INFO) << __func__ << ": status" << status; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackChanged, Unretained(McpServer::Get()), status)); + } + + void TrackTitle(uint8_t* title) override { + LOG(INFO) << __func__ << ": title" << title; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackTitle, Unretained(McpServer::Get()), title)); + } + + void TrackPosition(int32_t position) override { + LOG(INFO) << __func__ << ": position" << position; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackPosition, Unretained(McpServer::Get()), position)); + } + + void TrackDuration(int32_t duration) override { + LOG(INFO) << __func__ << ": duration" << duration; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackDuration, Unretained(McpServer::Get()), duration)); + } + + void ContentControlId(uint8_t ccid) override { + LOG(INFO) << __func__ << ": ccid" << ccid; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::ContentControlId, Unretained(McpServer::Get()), ccid)); + } + + void PlayingOrderSupported(uint16_t order) override { + LOG(INFO) << __func__ << ": order" << order; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::PlayingOrderSupported, Unretained(McpServer::Get()), order)); + } + + void PlayingOrder(uint8_t value) override { + LOG(INFO) << __func__ << ": value" << value; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::PlayingOrder, Unretained(McpServer::Get()), value)); + } + + void SetActiveDevice(const RawAddress& address, int set_id, int profile) override { + LOG(INFO) << __func__ << ": set_id" << set_id<< ": device"<< address; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::SetActiveDevice, Unretained(McpServer::Get()), address, set_id, profile)); + } + + void DisconnectMcp(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device"<< address; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::DisconnectMcp, Unretained(McpServer::Get()), address)); + } + + void BondStateChange(const RawAddress& address, int state) override { + LOG(INFO) << __func__ << ": device"<< address << " state : " << state; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::BondStateChange, Unretained(McpServer::Get()), address, state)); + } + + void Cleanup(void) override { + LOG(INFO) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&McpServer::CleanUp)); + } + + void OnConnectionStateChange(int status, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " state=" << (int)status; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::OnConnectionStateChange, + Unretained(callbacks), status, address)); + } + + void MediaControlPointChangeReq(uint8_t state, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " state=" << (int)state; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::MediaControlPointChangeReq, + Unretained(callbacks), state, address)); + } + + void TrackPositionChangeReq(int32_t position) override { + LOG(INFO) << __func__ << " position=" << (int)position; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::TrackPositionChangeReq, + Unretained(callbacks), position)); + } + + void PlayingOrderChangeReq(uint32_t order) override { + LOG(INFO) << __func__ << ": order=" << order; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::PlayingOrderChangeReq, + Unretained(callbacks), order)); + } + + private: + McpServerCallbacks* callbacks; + }; +}//namespace + +const McpServerInterface* btif_mcp_server_get_interface(void) { + LOG(INFO) << __func__; + if (!McpServerInstance) + McpServerInstance.reset(new McpServerInterfaceImpl()); + return McpServerInstance.get(); +} diff --git a/le_audio/system/bt/btif/src/btif_pacs_client.cc b/le_audio/system/bt/btif/src/btif_pacs_client.cc new file mode 100644 index 0000000000000000000000000000000000000000..8c0867a6562c56b558002845015c369a6c45b7c0 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_pacs_client.cc @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_pacs_client_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include +#include +#include +#include "osi/include/thread.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::PacsClientCallbacks; +using bluetooth::bap::pacs::PacsClientInterface; + + +namespace { + +class PacsClientInterfaceImpl; +std::unique_ptr PacsClientInstance; + +class PacsClientInterfaceImpl + : public PacsClientInterface, + public PacsClientCallbacks { + ~PacsClientInterfaceImpl() = default; + + void Init(PacsClientCallbacks* callbacks) override { + DVLOG(2) << __func__; + this->callbacks = callbacks; + + do_in_bta_thread( + FROM_HERE, + Bind(&PacsClient::Initialize, this)); + } + + void OnInitialized(int status, int client_id) override { + do_in_jni_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnInitialized, + Unretained(callbacks), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + ConnectionState state) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_jni_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnConnectionState, + Unretained(callbacks), address, state)); + } + + void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) override { + do_in_jni_thread(FROM_HERE, + Bind(&PacsClientCallbacks::OnAudioContextAvailable, + Unretained(callbacks), + address, available_contexts)); + } + + void OnSearchComplete(int status, + const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + do_in_jni_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnSearchComplete, + Unretained(callbacks), + status, + address, + sink_pac_records, + src_pac_records, + sink_locations, + src_locations, + available_contexts, + supported_contexts)); + } + + void Connect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::Connect, + Unretained(PacsClient::Get()), + client_id, address, false)); + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::Disconnect, + Unretained(PacsClient::Get()), + client_id, address)); + } + + void StartDiscovery(uint16_t client_id, + const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::StartDiscovery, + Unretained(PacsClient::Get()), + client_id, address)); + } + + void GetAvailableAudioContexts(uint16_t client_id, + const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::GetAudioAvailability, + Unretained(PacsClient::Get()), + client_id, address)); + } + + void Cleanup(uint16_t client_id) override { + DVLOG(2) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::CleanUp, client_id)); + } + + private: + PacsClientCallbacks* callbacks; +}; + +} // namespace + +PacsClientInterface* btif_pacs_client_get_interface() { + if (!PacsClientInstance) + PacsClientInstance.reset(new PacsClientInterfaceImpl()); + + return PacsClientInstance.get(); +} diff --git a/le_audio/system/bt/btif/src/btif_vcp_controller.cc b/le_audio/system/bt/btif/src/btif_vcp_controller.cc new file mode 100644 index 0000000000000000000000000000000000000000..10ba2acab167f4f74cfacabbb0ad0911b7c4c1e5 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_vcp_controller.cc @@ -0,0 +1,131 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +/* Volume Control Profile Interface */ + +#include "bt_target.h" +#include "bta_closure_api.h" +#include "bta_vcp_controller_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include +#include +#include + +using base::Bind; +using base::Unretained; +using bluetooth::vcp_controller::ConnectionState; +using bluetooth::vcp_controller::VcpControllerCallbacks; +using bluetooth::vcp_controller::VcpControllerInterface; + +namespace { +class VcpControllerInterfaceImpl; +std::unique_ptr VcpControllerInstance; + +class VcpControllerInterfaceImpl + : public VcpControllerInterface, public VcpControllerCallbacks { + ~VcpControllerInterfaceImpl() = default; + + void Init(VcpControllerCallbacks* callbacks) override { + LOG(INFO) << __func__ ; + this->callbacks = callbacks; + + do_in_bta_thread( + FROM_HERE, + Bind(&VcpController::Initialize, this)); + } + + void OnConnectionState(ConnectionState state, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " state=" << (int)state; + do_in_jni_thread(FROM_HERE, Bind(&VcpControllerCallbacks::OnConnectionState, + Unretained(callbacks), state, address)); + } + + void OnVolumeStateChange(uint8_t volume, uint8_t mute, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " volume=" << loghex(volume) + << " mute=" << (int)mute; + do_in_jni_thread(FROM_HERE, Bind(&VcpControllerCallbacks::OnVolumeStateChange, + Unretained(callbacks), volume, mute, address)); + } + + void OnVolumeFlagsChange(uint8_t flags, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " flags=" << loghex(flags); + do_in_jni_thread(FROM_HERE, Bind(&VcpControllerCallbacks::OnVolumeFlagsChange, + Unretained(callbacks), flags, address)); + } + + void Connect(const RawAddress& address, bool isDirect) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Connect, + Unretained(VcpController::Get()), address, isDirect)); + } + + void Disconnect(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Disconnect, + Unretained(VcpController::Get()), address)); + } + + void SetAbsVolume(uint8_t volume, const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " volume=" << loghex(volume); + do_in_bta_thread(FROM_HERE, Bind(&VcpController::SetAbsVolume, + Unretained(VcpController::Get()), address, volume)); + } + + void Mute(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Mute, + Unretained(VcpController::Get()), address)); + } + + void Unmute(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Unmute, + Unretained(VcpController::Get()), address)); + } + + void Cleanup(void) override { + LOG(INFO) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::CleanUp)); + } + + private: + VcpControllerCallbacks* callbacks; +}; + +} // namespace + +VcpControllerInterface* btif_vcp_get_controller_interface() { + LOG(INFO) << __func__; + if (!VcpControllerInstance) + VcpControllerInstance.reset(new VcpControllerInterfaceImpl()); + + return VcpControllerInstance.get(); +} + diff --git a/le_audio/system/bt/btif/src/btif_vmcp.cc b/le_audio/system/bt/btif/src/btif_vmcp.cc new file mode 100644 index 0000000000000000000000000000000000000000..844ac9bd91a3ce3c8a66a94bf923dfe2f0dae784 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_vmcp.cc @@ -0,0 +1,775 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#include +#include +#include +#include +#include + +#include "bt_types.h" +#include "bt_trace.h" + +#include +#include "btif_bap_codec_utils.h" +#include "btif_vmcp.h" +#include "btif_api.h" +#include + +using namespace std; + +unsigned long voice_codec_count, media_codec_count, qos_settings_count; + +// holds the value of current profile being parsed from xml +uint8_t current_profile = 1; + +std::vectorvmcp_voice_codec; +std::vectorvmcp_media_codec; +std::vectorvmcp_qos_low_lat_voice; +std::vectorvmcp_qos_low_lat_media; +std::vectorvmcp_qos_high_rel_media; + +std::vectorbap_voice_codec; +std::vectorbap_media_codec; +std::vectorbap_qos_low_lat_voice; +std::vectorbap_qos_low_lat_media; +std::vectorbap_qos_high_rel_media; + +std::vectorgcp_voice_codec; +std::vectorgcp_media_codec; +std::vectorgcp_qos_low_lat_voice; +std::vectorgcp_qos_low_lat_media; + +std::vectorwmcp_media_codec; +std::vectorwmcp_qos_high_rel_media; + +vector get_all_codec_configs(uint8_t profile, uint8_t context) +{ + vector ret_config; + CodecConfig temp_config; + vector *vptr = NULL; + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) { + vptr = &vmcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &vmcp_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &vmcp_voice_codec; + } + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) { + vptr = &bap_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &bap_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &bap_voice_codec; + } + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) { + vptr = &gcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &gcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &gcp_media_codec; + } + } else if (profile == WMCP) { + if(context == MEDIA_CONTEXT) { + vptr = &wmcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &wmcp_media_codec; + } + } + + if (!vptr){ + return { }; + } + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + memset(&temp_config, 0, sizeof(CodecConfig)); + + temp_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + + switch (vptr->at(i).freq_in_hz) + { + case SAMPLE_RATE_8000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + break; + case SAMPLE_RATE_16000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + break; + case SAMPLE_RATE_24000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + break; + case SAMPLE_RATE_32000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + break; + case SAMPLE_RATE_44100: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + break; + case SAMPLE_RATE_48000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + break; + default: + break; + } + + if (vptr->at(i).frame_dur_msecs == FRM_DURATION_7_5_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + else if (vptr->at(i).frame_dur_msecs == FRM_DURATION_10_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_10)); + + UpdateOctsPerFrame(&temp_config, vptr->at(i).oct_per_codec_frm); + + ret_config.push_back(temp_config); + } + + return ret_config; +} + +vector get_preferred_codec_configs(uint8_t profile, uint8_t context) +{ + vector ret_config; + CodecConfig temp_config; + vector *vptr = NULL; + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) { + vptr = &vmcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &vmcp_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &vmcp_voice_codec; + } + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) { + vptr = &bap_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &bap_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &bap_voice_codec; + } + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) { + vptr = &gcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &gcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &gcp_media_codec; + } + } else if (profile == WMCP) { + if(context == MEDIA_CONTEXT) { + vptr = &wmcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &wmcp_media_codec; + } + } + + if (!vptr) { + return {}; + } + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + if (vptr->at(i).mandatory == 1) { + memset(&temp_config, 0, sizeof(CodecConfig)); + + temp_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + + switch (vptr->at(i).freq_in_hz) + { + case SAMPLE_RATE_8000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + break; + case SAMPLE_RATE_16000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + break; + case SAMPLE_RATE_24000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + break; + case SAMPLE_RATE_32000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + break; + case SAMPLE_RATE_44100: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + break; + case SAMPLE_RATE_48000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + break; + default: + break; + } + + if (vptr->at(i).frame_dur_msecs == FRM_DURATION_7_5_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + else if (vptr->at(i).frame_dur_msecs == FRM_DURATION_10_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_10)); + + UpdateOctsPerFrame(&temp_config, vptr->at(i).oct_per_codec_frm); + + ret_config.push_back(temp_config); + } + } + + return ret_config; +} + +vector get_all_qos_params(uint8_t profile, uint8_t context) +{ + vector ret_config; + QoSConfig temp_config; + vector *vptr = NULL; + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) + vptr = &vmcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &vmcp_qos_low_lat_media; + else if (context == MEDIA_HR_CONTEXT) + vptr = &vmcp_qos_high_rel_media; + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) + vptr = &bap_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &bap_qos_low_lat_media; + else if (context == MEDIA_HR_CONTEXT) + vptr = &bap_qos_high_rel_media; + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) + vptr = &gcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &gcp_qos_low_lat_media; + } else if (profile == WMCP) { + if (context == MEDIA_HR_CONTEXT) + vptr = &wmcp_qos_high_rel_media; + } + + if (!vptr) { + return {}; + } + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + memset(&temp_config, 0, sizeof(QoSConfig)); + + switch (vptr->at(i).freq_in_hz) + { + case SAMPLE_RATE_8000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + break; + case SAMPLE_RATE_16000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + break; + case SAMPLE_RATE_24000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + break; + case SAMPLE_RATE_32000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + break; + case SAMPLE_RATE_44100: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + break; + case SAMPLE_RATE_48000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + break; + default: + break; + } + + temp_config.sdu_int_micro_secs = vptr->at(i).sdu_int_micro_secs; + temp_config.framing = vptr->at(i).framing; + temp_config.max_sdu_size = vptr->at(i).max_sdu_size; + temp_config.retrans_num = vptr->at(i).retrans_num; + temp_config.max_trans_lat = vptr->at(i).max_trans_lat; + temp_config.presentation_delay = vptr->at(i).presentation_delay; + temp_config.mandatory = vptr->at(i).mandatory; + + ret_config.push_back(temp_config); + } + + return ret_config; +} + +vector get_qos_params_for_codec(uint8_t profile, uint8_t context, CodecSampleRate freq, uint8_t frame_dur, uint8_t octets) +{ + vector ret_config; + QoSConfig temp_config; + vector *vptr = NULL; + uint32_t frame_dur_micro_sec = 0; + uint32_t local_freq = 0; + + if (frame_dur == static_cast(CodecFrameDuration::FRAME_DUR_7_5)) + frame_dur_micro_sec = FRM_DURATION_7_5_MS * 1000; + else if (frame_dur == static_cast(CodecFrameDuration::FRAME_DUR_10)) + frame_dur_micro_sec = FRM_DURATION_10_MS * 1000; + + switch (freq) + { + case CodecSampleRate::CODEC_SAMPLE_RATE_8000: + local_freq = SAMPLE_RATE_8000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_16000: + local_freq = SAMPLE_RATE_16000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_24000: + local_freq = SAMPLE_RATE_24000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_32000: + local_freq = SAMPLE_RATE_32000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_44100: + local_freq = SAMPLE_RATE_44100; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_48000: + local_freq = SAMPLE_RATE_48000; + break; + default: + break; + } + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) + vptr = &vmcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &vmcp_qos_low_lat_media; + else if (context == MEDIA_HR_CONTEXT) + vptr = &vmcp_qos_high_rel_media; + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) + vptr = &bap_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) { + BTIF_TRACE_IMP("%s: filling BAP LL vptr", __func__); + vptr = &bap_qos_low_lat_media; + } else if (context == MEDIA_HR_CONTEXT) { + BTIF_TRACE_IMP("%s: filling BAP HR vptr", __func__); + vptr = &bap_qos_high_rel_media; + } + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) + vptr = &gcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &gcp_qos_low_lat_media; + } else if (profile == WMCP) { + if (context == MEDIA_HR_CONTEXT) { + BTIF_TRACE_IMP("%s: filling WMCP HR vptr", __func__); + vptr = &wmcp_qos_high_rel_media; + } + } + + if (!vptr) { + return { }; + } + + BTIF_TRACE_IMP("%s: vptr size: %d", __func__, (uint8_t)vptr->size()); + BTIF_TRACE_IMP("%s: local_freq: %d, frame_dur_micro_sec: %d, octets: %d", + __func__, local_freq, frame_dur_micro_sec, octets); + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + BTIF_TRACE_IMP("%s: freq_in_hz: %d, sdu_int_micro_secs: %d, max_sdu_size: %d", + __func__, vptr->at(i).freq_in_hz, vptr->at(i).sdu_int_micro_secs, + vptr->at(i).max_sdu_size); + if (vptr->at(i).freq_in_hz == local_freq && + vptr->at(i).sdu_int_micro_secs == frame_dur_micro_sec && + vptr->at(i).max_sdu_size == octets) { + BTIF_TRACE_IMP("%s: Local and vptr matched.", __func__); + memset(&temp_config, 0, sizeof(QoSConfig)); + + temp_config.sample_rate = freq; + temp_config.sdu_int_micro_secs = vptr->at(i).sdu_int_micro_secs; + temp_config.framing = vptr->at(i).framing; + temp_config.max_sdu_size = vptr->at(i).max_sdu_size; + temp_config.retrans_num = vptr->at(i).retrans_num; + temp_config.max_trans_lat = vptr->at(i).max_trans_lat; + temp_config.presentation_delay = vptr->at(i).presentation_delay; + temp_config.mandatory = vptr->at(i).mandatory; + + ret_config.push_back(temp_config); + } + } + + return ret_config; +} + +bool is_leaf(xmlNode *node) +{ + xmlNode *child = node->children; + while(child) + { + if(child->type == XML_ELEMENT_NODE) + return false; + + child = child->next; + } + + return true; +} + +void parseCodecConfigs(xmlNode *input_node, int context) +{ + stack profile_node_stack; + unsigned int TempCodecCount = 0; + unsigned int TempFieldsCount = 0; + xmlNode *FirstChild = xmlFirstElementChild(input_node); + unsigned long CodecFields = xmlChildElementCount(FirstChild); + codec_config temp_codec_config; + memset(&temp_codec_config, 0, sizeof(codec_config)); + + + BTIF_TRACE_IMP("codec Fields count is %ld \n", CodecFields); + for (xmlNode *node = input_node->children; node != NULL || !profile_node_stack.empty(); node = node->children) + { + if (node == NULL) + { + node = profile_node_stack.top(); + profile_node_stack.pop(); + } + + if(node) + { + if(node->type == XML_ELEMENT_NODE) + { + if((is_leaf(node))) + { + string content = (const char*)(xmlNodeGetContent(node)); + if (content[0] == '\0') { + return; + } + if(!xmlStrcmp(node->name,(const xmlChar*)"SamplingFrequencyInHz")) + { + temp_codec_config.freq_in_hz = atoi(content.c_str()); + TempFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"FrameDurationInMicroSecs")) + { + temp_codec_config.frame_dur_msecs = (float)atoi(content.c_str())/1000; + TempFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"OctetsPerCodecFrame")) + { + temp_codec_config.oct_per_codec_frm = atoi(content.c_str()); + TempFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"Mandatory")) + { + temp_codec_config.mandatory = atoi(content.c_str()); + TempFieldsCount++; + } + } + if(TempFieldsCount == CodecFields) + { + if (current_profile == VMCP) { + if (context == VOICE_CONTEXT) { + vmcp_voice_codec.push_back(temp_codec_config); + } else if (context == MEDIA_CONTEXT) { + vmcp_media_codec.push_back(temp_codec_config); + } + } else if (current_profile == BAP) { + if (context == VOICE_CONTEXT) { + bap_voice_codec.push_back(temp_codec_config); + } else if (context == MEDIA_CONTEXT) { + bap_media_codec.push_back(temp_codec_config); + } + } else if (current_profile == GCP) { + if (context == VOICE_CONTEXT) { + gcp_voice_codec.push_back(temp_codec_config); + } else if (context == MEDIA_CONTEXT) { + gcp_media_codec.push_back(temp_codec_config); + } + } else if (current_profile == WMCP) { + if (context == MEDIA_CONTEXT) { + BTIF_TRACE_IMP("%s: parsed codec config for wmcp", __func__); + wmcp_media_codec.push_back(temp_codec_config); + } + } + + TempFieldsCount = 0; + TempCodecCount++; + } + } + + if(node->next != NULL) + { + profile_node_stack.push(node->next); + node = node->next; + } + } // end of if (node) + } // end of for + if(context == VOICE_CONTEXT && TempCodecCount == voice_codec_count) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld CG codecs are parsed successfully\n", voice_codec_count); + } else { + BTIF_TRACE_IMP("All %ld GAT Rx codecs are parsed successfully\n", voice_codec_count); + } + } + else if(context == MEDIA_CONTEXT && TempCodecCount == media_codec_count) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld UMS codecs are parsed successfully\n", media_codec_count); + } else if (current_profile == GCP) { + BTIF_TRACE_IMP("All %ld GAT Tx codecs are parsed successfully\n", media_codec_count); + } else if (current_profile == WMCP) { + BTIF_TRACE_IMP("All %ld WM Rx codecs are parsed successfully\n", media_codec_count); + } + } +} + +void parseQoSConfigs(xmlNode *QoSInputNode, int context) +{ + stack QoS_Stack; + unsigned int TempQoSCodecCount = 0; + unsigned int TempQoSFieldsCount = 0; + xmlNode * FirstChild = xmlFirstElementChild(QoSInputNode); + unsigned long QoSCodecFields = xmlChildElementCount(FirstChild); + qos_config temp_qos_config ; + memset(&temp_qos_config, 0, sizeof(qos_config)); + + BTIF_TRACE_IMP("QoS Fields count %ld \n", QoSCodecFields); + for (xmlNode *node = QoSInputNode->children; node != NULL || !QoS_Stack.empty(); node = node->children) + { + if (node == NULL) + { + node = QoS_Stack.top(); + QoS_Stack.pop(); + } + if(node) + { + if(node->type == XML_ELEMENT_NODE) + { + if(is_leaf(node)) + { + string content = (const char*)(xmlNodeGetContent(node)); + if (content[0] == '\0') { + return; + } + if(!xmlStrcmp(node->name,(const xmlChar*)"SamplingFrequencyInHz")) + { + temp_qos_config.freq_in_hz = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"SDUIntervalInMicroSecs")) + { + temp_qos_config.sdu_int_micro_secs = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"Framing")) + { + temp_qos_config.framing = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"MaxSDUSize")) + { + temp_qos_config.max_sdu_size = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"RetransmissionNumber")) + { + temp_qos_config.retrans_num = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"MaxTransportLatency")) + { + temp_qos_config.max_trans_lat = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"PresentationDelay")) + { + temp_qos_config.presentation_delay = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"Mandatory")) + { + temp_qos_config.mandatory = atoi(content.c_str()); + TempQoSFieldsCount++; + } + } + if(TempQoSFieldsCount == QoSCodecFields) + { + if(current_profile == VMCP) { + if (context == VOICE_CONTEXT) { + vmcp_qos_low_lat_voice.push_back(temp_qos_config); + } else if (context == MEDIA_LL_CONTEXT) { + vmcp_qos_low_lat_media.push_back(temp_qos_config); + } else if (context == MEDIA_HR_CONTEXT) { + vmcp_qos_high_rel_media.push_back(temp_qos_config); + } + } else if(current_profile == BAP) { + if (context == VOICE_CONTEXT) { + bap_qos_low_lat_voice.push_back(temp_qos_config); + } else if (context == MEDIA_LL_CONTEXT) { + bap_qos_low_lat_media.push_back(temp_qos_config); + } else if (context == MEDIA_HR_CONTEXT) { + bap_qos_high_rel_media.push_back(temp_qos_config); + } + } else if(current_profile == GCP) { + if (context == VOICE_CONTEXT) { + gcp_qos_low_lat_voice.push_back(temp_qos_config); + } else if (context == MEDIA_LL_CONTEXT) { + gcp_qos_low_lat_media.push_back(temp_qos_config); + } + } else if(current_profile == WMCP) { + if (context == MEDIA_HR_CONTEXT) { + BTIF_TRACE_IMP("%s: parsed qos config for wmcp", __func__); + wmcp_qos_high_rel_media.push_back(temp_qos_config); + } + } + + TempQoSFieldsCount = 0; + TempQoSCodecCount++; + } + } + if(node->next != NULL) + { + QoS_Stack.push(node->next); + node = node->next; + } + + } + } + if(TempQoSCodecCount == qos_settings_count) + { + if(context == VOICE_CONTEXT) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld CG Qos Config are parsed successfully\n", qos_settings_count); + } else { + BTIF_TRACE_IMP("All %ld GAT Rx Qos Config are parsed successfully\n", qos_settings_count); + } + } + else if(context == MEDIA_CONTEXT) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld UMS Qos Config are parsed successfully\n", qos_settings_count); + } else if (current_profile == GCP) { + BTIF_TRACE_IMP("All %ld GAT Tx Qos Config are parsed successfully\n", qos_settings_count); + } else if (current_profile == WMCP) { + BTIF_TRACE_IMP("All %ld WM Rx Qos Config are parsed successfully\n", qos_settings_count); + } + } + } +} + +void parse_xml(xmlNode *inputNode) +{ + stack S; + for (xmlNode *node = inputNode; node != NULL || !S.empty(); node = node->children) + { + if (node == NULL) + { + node = S.top(); + S.pop(); + } + if (node) + { + if (node->type == XML_ELEMENT_NODE) + { + if (!(is_leaf(node))) + { + string content = (const char *) (xmlNodeGetContent (node)); + if (content[0] == '\0') { + return; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "VMCP")) + { + BTIF_TRACE_IMP("VMCP configs being parsed\n"); + current_profile = VMCP; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "BAP")) + { + BTIF_TRACE_IMP("BAP configs being parsed\n"); + current_profile = BAP; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "GCP")) + { + BTIF_TRACE_IMP("GCP configs being parsed\n"); + current_profile = GCP; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "WMCP")) + { + BTIF_TRACE_IMP("WMCP configs being parsed\n"); + current_profile = WMCP; + } + + if (!xmlStrcmp (node->name, (const xmlChar *) "CodecCapabilitiesForVoice")) + { + voice_codec_count = xmlChildElementCount(node); + parseCodecConfigs(node, VOICE_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "CodecCapabilitiesForMedia")) + { + media_codec_count = xmlChildElementCount(node); + parseCodecConfigs(node, MEDIA_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "QosSettingsForLowLatencyVoice")) + { + qos_settings_count = xmlChildElementCount(node); + parseQoSConfigs(node, VOICE_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "QosSettingsForLowLatencyMedia")) + { + qos_settings_count = xmlChildElementCount(node); + parseQoSConfigs(node, MEDIA_LL_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "QosSettingsForHighReliabilityMedia")) + { + qos_settings_count = xmlChildElementCount(node); + parseQoSConfigs(node, MEDIA_HR_CONTEXT); + } + } + } + if(node->next != NULL) + { + S.push(node -> next); + } + } + } +} + +void btif_vmcp_init() { + xmlDoc *doc = NULL; + xmlNode *root_element = NULL; + + doc = xmlReadFile(LEAUDIO_CONFIG_PATH, NULL, 0); + if (doc == NULL) { + BTIF_TRACE_ERROR("Could not parse the XML file"); + } + + root_element = xmlDocGetRootElement(doc); + parse_xml(root_element); + xmlFreeDoc(doc); + xmlCleanupParser(); + + //Register Audio Gaming Service UUID (GCP) with Gattc + btif_register_uuid_srvc_disc(bluetooth::Uuid::FromString("12994b7e-6d47-4215-8c9e-aae9a1095ba3")); + + //Register Wireless Microphone Configuration Service UUID (WMCP) with Gattc + btif_register_uuid_srvc_disc(bluetooth::Uuid::FromString("2587db3c-ce70-4fc9-935f-777ab4188fd7")); +} diff --git a/le_audio/system/bt/common/state_machine.h b/le_audio/system/bt/common/state_machine.h new file mode 100644 index 0000000000000000000000000000000000000000..62d92d2636f2f4846adf103389de48d06b070968 --- /dev/null +++ b/le_audio/system/bt/common/state_machine.h @@ -0,0 +1,214 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace bluetooth { + +namespace common { + +/** + * State machine used by Bluetooth native stack. + */ +class StateMachine { + public: + enum { kStateInvalid = -1 }; + + /** + * A class to represent the state in the State Machine. + */ + class State { + friend class StateMachine; + + public: + /** + * Constructor. + * + * @param sm the State Machine to use + * @param state_id the unique State ID. It should be a non-negative number. + */ + State(StateMachine& sm, int state_id) : sm_(sm), state_id_(state_id) {} + + virtual ~State() = default; + + /** + * Process an event. + * TODO: The arguments are wrong - used for backward compatibility. + * Will be replaced later. + * + * @param event the event type + * @param p_data the event data + * @return true if the processing was completed, otherwise false + */ + virtual bool ProcessEvent(uint32_t event, void* p_data) = 0; + + /** + * Get the State ID. + * + * @return the State ID + */ + int StateId() const { return state_id_; } + + protected: + /** + * Called when a state is entered. + */ + virtual void OnEnter() {} + + /** + * Called when a state is exited. + */ + virtual void OnExit() {} + + /** + * Transition the State Machine to a new state. + * + * @param dest_state_id the state ID to transition to. It must be one + * of the unique state IDs when the corresponding state was created. + */ + void TransitionTo(int dest_state_id) { sm_.TransitionTo(dest_state_id); } + + /** + * Transition the State Machine to a new state. + * + * @param dest_state the state to transition to. It cannot be nullptr. + */ + void TransitionTo(StateMachine::State* dest_state) { + sm_.TransitionTo(dest_state); + } + + private: + StateMachine& sm_; + int state_id_; + }; + + StateMachine() + : initial_state_(nullptr), + previous_state_(nullptr), + current_state_(nullptr) {} + ~StateMachine() { + for (auto& kv : states_) delete kv.second; + } + + /** + * Start the State Machine operation. + */ + void Start() { TransitionTo(initial_state_); } + + /** + * Quit the State Machine operation. + */ + void Quit() { previous_state_ = current_state_ = nullptr; } + + /** + * Get the current State ID. + * + * @return the current State ID + */ + int StateId() const { + if (current_state_ != nullptr) { + return current_state_->StateId(); + } + return kStateInvalid; + } + + /** + * Get the previous current State ID. + * + * @return the previous State ID + */ + int PreviousStateId() const { + if (previous_state_ != nullptr) { + return previous_state_->StateId(); + } + return kStateInvalid; + } + + /** + * Process an event. + * TODO: The arguments are wrong - used for backward compatibility. + * Will be replaced later. + * + * @param event the event type + * @param p_data the event data + * @return true if the processing was completed, otherwise false + */ + bool ProcessEvent(uint32_t event, void* p_data) { + if (current_state_ == nullptr) return false; + return current_state_->ProcessEvent(event, p_data); + } + + /** + * Transition the State Machine to a new state. + * + * @param dest_state_id the state ID to transition to. It must be one + * of the unique state IDs when the corresponding state was created. + */ + void TransitionTo(int dest_state_id) { + auto it = states_.find(dest_state_id); + + CHECK(it != states_.end()) << "Unknown State ID: " << dest_state_id; + State* dest_state = it->second; + TransitionTo(dest_state); + } + + /** + * Transition the State Machine to a new state. + * + * @param dest_state the state to transition to. It cannot be nullptr. + */ + void TransitionTo(StateMachine::State* dest_state) { + if (current_state_ != nullptr) { + current_state_->OnExit(); + } + previous_state_ = current_state_; + current_state_ = dest_state; + current_state_->OnEnter(); + } + + /** + * Add a state to the State Machine. + * The state machine takes ownership on the state - i.e., the state will + * be deleted by the State Machine itself. + * + * @param state the state to add + */ + void AddState(State* state) { + states_.insert(std::make_pair(state->StateId(), state)); + } + + /** + * Set the initial state of the State Machine. + * + * @param initial_state the initial state + */ + void SetInitialState(State* initial_state) { initial_state_ = initial_state; } + + private: + State* initial_state_; + State* previous_state_; + State* current_state_; + std::map states_; +}; + +} // namespace common + +} // namespace bluetooth diff --git a/le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h b/le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h new file mode 100644 index 0000000000000000000000000000000000000000..7137867898ec4cddf7d3dfba32b22a19ea4e0954 --- /dev/null +++ b/le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h @@ -0,0 +1,62 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace bluetooth { +namespace call_control { + +/** + * CCS/GTBS related callbacks invoked from from the Bluetooth native stack + * All callbacks are invoked on the JNI thread + */ +class CallControllerCallbacks { + public: + virtual ~CallControllerCallbacks() = default; + /** + * Callback for notifying the CCS initialization status. + * + * @param state success if zero, failure otherwise + */ + virtual void CallControlInitializedCallback(uint8_t state + ) = 0; + /** + * Callback for connection state change. + * + * @param state one of the values from btcc_connection_state_t + * @param bd_addr remote device address + */ + virtual void ConnectionStateCallback(uint8_t state, + const RawAddress& address) = 0; + /** + * Callback for call control operations. + * + * @param op call control operation initiated from remote + * @param indicies Indicies for the call control operations + * @param count number of Indicies for the call control operations + * @uri uri for the call control operation + * @param bd_addr remote device address which initiated the call control op + */ + virtual void CallControlCallback(uint8_t op, std::vector indicies, int count, std::vector uri_data, + const RawAddress& address) = 0; +}; + +} // namespace callcontrol +} // namespace bluetooth diff --git a/le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h b/le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h new file mode 100644 index 0000000000000000000000000000000000000000..c2b784989950b2cd71b1707d6e1b2a63f2196dc0 --- /dev/null +++ b/le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h @@ -0,0 +1,152 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "bluetooth_callcontrol_callbacks.h" +#include + +#define BT_PROFILE_CC_ID "cc_server" + +namespace bluetooth { +namespace call_control { + +/** + * Programming interface for CCS/GTBS profiles in the Fluoride stack + * Thread-safe + */ +class CallControllerInterface { + public: + virtual ~CallControllerInterface() = default; + /** + * Initialize the CCS/GTBS Interface + * + * @param callbacks callbacks for the user of the native stack + * @param max_ccs_clients maximum number of CCS/GTBS clients + * @param inband_ringing_enabled whether inband ringtone is enabled + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t Init(CallControllerCallbacks* callbacks, Uuid uuid, int max_ccs_clients, + bool inband_ringing_enabled) = 0; + /** + * Updates telephony bearer name + * + * @param operator_str bearer name of provider + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateBearerName(uint8_t* operator_str) = 0; + + /** + * Updates the bearer Technogly type + * + * @param bearer_tech bearer technology type of provider + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateBearerTechnology(int bearer_tech) = 0; + + /** + * Updates telephony network bearers supported + * + * @param supportedBearers supported bearers list + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateSupportedBearerList(uint8_t* supportedBearers) = 0; + + /** + * Updates optional Call control operations supported + * + * @param feature bitmask value representing the optional op code supported + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t CallControlOptionalOpSupported(int feature) = 0; + + /** + * Update network signal strength + * + * @param signal level + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateSignalStatus(int signal) = 0; + + /** + * Update status flag for GTBS + * + * @param bd_addr remote device address + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateStatusFlags(uint8_t status_flag) = 0; + + /** + * Update Content control for GTBS/CCS + * + * @param ccid content control Id for GTBS/CCS + * @return BT_STATUS_SUCCESS on success + */ + virtual void ContentControlId(uint32_t ccid) = 0; + + /** + * Update the Call State of CCS + * + * @param len of call state infos + * @param call_state_list array of call state list, where each call state + * comprised of index, state, flags + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t CallState(int len, std::vector call_state_list) = 0; + + /** + * Update Incoming call URI for the given Call Index + * + * @param index index of the call + * @param uri representing the Incoming call, will be Incoming call number for telephony + * @return BT_STATUS_SUCCESS on success + */ + + virtual void UpdateIncomingCall(int index, uint8_t* uri) = 0; + /** + * Response for Call control Operation initiated + * + * @param op Operation for which response is sent + * @param index index of call for operation + * @param status status of the operation performed + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address) = 0; + + /** + * Set the current active device for GTBS/CCS + * + * @param active_device_addr remote device address + */ + virtual void SetActiveDevice(const RawAddress& address, int set_id) = 0; + + /** + * Disconnect GTBS/CTS profile for remote + * @param address remote device address + */ + virtual void Disconnect(const RawAddress& address) = 0; + /** + * Closes the GTBS/CCS interface. + */ + virtual void Cleanup() = 0; +}; + +} // namespace call_control +} // namespace bluetooth diff --git a/le_audio/vhal/include/hardware/bt_acm.h b/le_audio/vhal/include/hardware/bt_acm.h new file mode 100644 index 0000000000000000000000000000000000000000..f32fb90d96d56b8df3ebdb7ca9a588ebffa380a8 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_acm.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#ifndef ANDROID_INCLUDE_BT_ACM_H +#define ANDROID_INCLUDE_BT_ACM_H + +#include + +#include +#include +#include + +__BEGIN_DECLS + +#define BT_PROFILE_ACM_ID "bt_acm_proflie" +using bluetooth::bap::pacs::CodecConfig; +/* Bluetooth ACM connection states */ +typedef enum { + BTACM_CONNECTION_STATE_DISCONNECTED = 0, + BTACM_CONNECTION_STATE_CONNECTING, + BTACM_CONNECTION_STATE_CONNECTED, + BTACM_CONNECTION_STATE_DISCONNECTING +} btacm_connection_state_t; + +/* Bluetooth ACM datapath states */ +typedef enum { + BTACM_AUDIO_STATE_REMOTE_SUSPEND = 0, + BTACM_AUDIO_STATE_STOPPED, + BTACM_AUDIO_STATE_STARTED, +} btacm_audio_state_t; + +/** Callback for connection state change. + * state will have one of the values from btacm_connection_state_t + */ +typedef void (*btacm_connection_state_callback)(const RawAddress& bd_addr, + btacm_connection_state_t state, + uint16_t contextType); + +/** Callback for audiopath state change. + * state will have one of the values from btacm_audio_state_t + */ +typedef void (*btacm_audio_state_callback)(const RawAddress& bd_addr, + btacm_audio_state_t state, + uint16_t contextType); + +/** Callback for audio configuration change. + * Used only for the ACM Initiator interface. + */ +typedef void (*btacm_audio_config_callback)( + const RawAddress& bd_addr, CodecConfig codec_config, + std::vector codecs_local_acmabilities, + std::vector codecs_selectable_acmabilities, + uint16_t contextType); + +/** BT-ACM Initiator callback structure. */ +typedef struct { + /** set to sizeof(btacm_initiator_callbacks_t) */ + size_t size; + btacm_connection_state_callback connection_state_cb; + btacm_audio_state_callback audio_state_cb; + btacm_audio_config_callback audio_config_cb; +} btacm_initiator_callbacks_t; + +/** Represents the standard BT-ACM Initiator interface. + */ +typedef struct { + /** set to sizeof(btacm_source_interface_t) */ + size_t size; + /** + * Register the BtAcm callbacks. + */ + bt_status_t (*init)( + btacm_initiator_callbacks_t* callbacks, int max_connected_audio_devices, + const std::vector& codec_priorities); + + /** connect to headset */ + bt_status_t (*connect)(const RawAddress& bd_addr, uint16_t context_type, + uint16_t profile_type, uint16_t preferred_context); + + /** dis-connect from headset */ + bt_status_t (*disconnect)(const RawAddress& bd_addr, uint16_t context_type); + + /** sets the connected device as active */ + bt_status_t (*set_active_device)(const RawAddress& bd_addr, + uint16_t context_type); + + /** start stream */ + bt_status_t (*start_stream)(const RawAddress& bd_addr, uint16_t context_type); + + /** stop stream */ + bt_status_t (*stop_stream)(const RawAddress& bd_addr, uint16_t context_type); + + /** configure the codecs settings preferences */ + bt_status_t (*config_codec)( + const RawAddress& bd_addr, + std::vector codec_preferences, + uint16_t context_type, uint16_t preferred_context); + + /** configure the codec based on ID*/ + bt_status_t (*change_config_codec)( + const RawAddress& bd_addr, + char* Id); + + /** Closes the interface. */ + void (*cleanup)(void); + +} btacm_initiator_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_AV_H */ diff --git a/le_audio/vhal/include/hardware/bt_apm.h b/le_audio/vhal/include/hardware/bt_apm.h new file mode 100644 index 0000000000000000000000000000000000000000..2eb95e32ac2f0809797e776f98b165d085e3e719 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_apm.h @@ -0,0 +1,75 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#ifndef ANDROID_INCLUDE_BT_APM_H +#define ANDROID_INCLUDE_BT_APM_H + +#define BT_APM_MODULE_ID "apm" + +#include + +#include + +__BEGIN_DECLS + +/* Bluetooth APM Audio Types */ +typedef enum { + BTAPM_VOICE_AUDIO = 0, + BTAPM_MEDIA_AUDIO, + BTAPM_BROADCAST_AUDIO +} btapm_audio_type_t; + +void call_active_profile_info(const RawAddress& bd_addr, uint16_t audio_type); +int get_active_profile(const RawAddress& bd_addr, uint16_t audio_type); +typedef int (*btapm_active_profile_callback)(const RawAddress& bd_addr, uint16_t audio_type); + + +typedef struct { + size_t size; + btapm_active_profile_callback active_profile_cb; +}btapm_initiator_callbacks_t; + + + +/** APM interface + */ +typedef struct { + + /** set to sizeof(bt_apm_interface_t) */ + size_t size; + /** + * Register the BtAv callbacks. + */ + bt_status_t (*init)(btapm_initiator_callbacks_t* callbacks); + + /** updates new active device to stack */ + bt_status_t (*active_device_change)(const RawAddress& bd_addr, uint16_t profile, uint16_t audio_type); + + /** send content control id to stack */ + bt_status_t (*set_content_control_id)(uint16_t content_control_id, uint16_t audio_type); + + /** Closes the interface. */ + void (*cleanup)( void ); + +}bt_apm_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_APM_H */ + diff --git a/le_audio/vhal/include/hardware/bt_ascs_client.h b/le_audio/vhal/include/hardware/bt_ascs_client.h new file mode 100644 index 0000000000000000000000000000000000000000..cd9341b3d699a2538741114cea1c528647eaec58 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_ascs_client.h @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_INCLUDE_BT_ASCS_CLIENT_H +#define ANDROID_INCLUDE_BT_ASCS_CLIENT_H + +#include + +namespace bluetooth { +namespace bap { +namespace ascs { + +#define BT_PROFILE_ASCS_CLIENT_ID "bt_ascs_client" + +constexpr uint8_t ASE_DIRECTION_SINK = 0x01; +constexpr uint8_t ASE_DIRECTION_SOURCE = 0x02; + +constexpr uint32_t CONTEXT_TYPE_CONVERSATIONAL = 0x0002; +constexpr uint32_t CONTEXT_TYPE_MEDIA = 0x0004; + +constexpr uint8_t ASE_STATE_IDLE = 0x00; +constexpr uint8_t ASE_STATE_CODEC_CONFIGURED = 0x01; +constexpr uint8_t ASE_STATE_QOS_CONFIGURED = 0x02; +constexpr uint8_t ASE_STATE_ENABLING = 0x03; +constexpr uint8_t ASE_STATE_STREAMING = 0x04; +constexpr uint8_t ASE_STATE_DISABLING = 0x05; +constexpr uint8_t ASE_STATE_RELEASING = 0x06; +constexpr uint8_t ASE_STATE_INVALID = 0xFF; + +typedef uint8_t sdu_interval_t[3]; +typedef uint8_t presentation_delay_t[3]; +typedef uint8_t codec_type_t[5]; + + +enum class ASCSEvent { + ASCS_DISCOVERY_CMPL_EVT = 0, + ASCS_DEV_CONNECTED, + ASCS_DEV_DISCONNECTED, + ASCS_ASE_STATE, +}; + +struct AudioContext { + uint8_t length; + uint8_t type; + uint16_t value; +}; + +enum class AseState { + IDLE = 0, + CODEC_CONFIGURED, + QOS_CONFIGURED, + ENABLING, + STREAMING, + DISABLING, + RELEASING, +}; + +enum class AseOpId { + CODEC_CONFIG = 0x01, + QOS_CONFIG, + ENABLE, + START_READY, + DISABLE, + STOP_READY, + UPDATE_META_DATA, + RELEASE +}; + +enum class GattState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +struct AseCodecConfigOp { + uint8_t ase_id; + uint8_t tgt_latency; + uint8_t tgt_phy; + codec_type_t codec_id; + uint8_t codec_params_len; + std::vector codec_params; +} __attribute__((packed)); + +struct AseQosConfigOp { + uint8_t ase_id; + uint8_t cig_id; + uint8_t cis_id; + sdu_interval_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu_size; + uint8_t retrans_number; + uint16_t trans_latency; + presentation_delay_t present_delay; +} __attribute__((packed)); + +struct AseEnableOp { + uint8_t ase_id; + uint8_t meta_data_len; + std::vector meta_data; +} __attribute__((packed)); + +struct AseDisableOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseStartReadyOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseStopReadyOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseReleaseOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseUpdateMetadataOp { + uint8_t ase_id; + uint8_t meta_data_len; + std::vector meta_data; +} __attribute__((packed)); + +struct AseCodecConfigParams { + uint8_t framing; + uint8_t pref_phy; + uint8_t pref_rtn; + uint16_t mtl; + presentation_delay_t pd_min; + presentation_delay_t pd_max; + presentation_delay_t pref_pd_min; + presentation_delay_t pref_pd_max; + codec_type_t codec_id; + uint8_t codec_params_len; + std::vector codec_params; +} __attribute__((packed)); + +struct AseQosConfigParams { + uint8_t cig_id; + uint8_t cis_id; + sdu_interval_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu_size; + uint8_t rtn; + uint16_t mtl; + presentation_delay_t pd; +} __attribute__((packed)); + +struct AseGenericParams { + uint8_t cig_id; + uint8_t cis_id; + uint8_t meta_data_len; + std::vector meta_data; +} __attribute__((packed)); + +union AseOp { + AseCodecConfigOp codec_config_op; + AseQosConfigOp qos_config_op; + AseEnableOp enable_op; + AseDisableOp disable_op; + AseStartReadyOp start_ready_op; + AseStopReadyOp stop_ready_op; + AseReleaseOp release_op; +}; + +struct AseOpStatus { + uint8_t ase_id; + uint8_t resp_code; + uint8_t reason; +}; + +struct AseParams { + uint8_t ase_id; + uint8_t ase_state; + AseCodecConfigParams codec_config_params; + AseQosConfigParams qos_config_params; + AseGenericParams generic_params; +} __attribute__((packed)); + +struct AseCpNotification { + uint8_t ase_opcode; + uint8_t num_ases; + std::vector status; +} __attribute__((packed)); + +struct Ase { + uint16_t ase_handle; + uint16_t ase_ccc_handle; + AseParams ase_params; +} __attribute__((packed)); + +struct AscsDiscoveryDb { + std::vector ase_list; + uint16_t ase_cp_handle; + uint16_t ase_cp_ccc_handle; + bool service_changed_rcvd; + bool active; +}; + +class AscsClientCallbacks { + public: + virtual ~AscsClientCallbacks() = default; + + /** Callback for ascs server registration status */ + virtual void OnAscsInitialized(int status, int client_id) = 0; + + /** Callback for ascs server connection state change */ + virtual void OnConnectionState(const RawAddress& address, + GattState state) = 0; + + /** Callback for ascs server control op failed status */ + virtual void OnAseOpFailed(const RawAddress& address, + AseOpId ase_op_id, + std::vector status) = 0; + + /** Callback for ascs ase state change */ + virtual void OnAseState(const RawAddress& address, + AseParams ase) = 0; + + /** Callback for ascs discovery results */ + virtual void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) = 0; +}; + +class AscsClientInterface { + public: + virtual ~AscsClientInterface() = default; + + /** Register the Ascs client callbacks */ + virtual void Init(AscsClientCallbacks* callbacks) = 0; + + /** Connect to ascs server */ + virtual void Connect(uint16_t client_id, const RawAddress& address) = 0; + + /** Disconnect ascs server */ + virtual void Disconnect(uint16_t client_id, const RawAddress& address) = 0; + + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + + virtual void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) = 0; + + virtual void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs); + + virtual void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs); + + virtual void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops); + + virtual void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops); + + virtual void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops); + + virtual void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops); + + virtual void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops); + + virtual void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops); + + /** Closes the interface. */ + virtual void Cleanup(uint16_t client_id) = 0; +}; + +} // namespace ascs +} // namespace bap +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_CLIENT_H */ diff --git a/le_audio/vhal/include/hardware/bt_bap_ba.h b/le_audio/vhal/include/hardware/bt_bap_ba.h new file mode 100644 index 0000000000000000000000000000000000000000..58c5fbd91331b8955e0f9706091c659797255507 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_bap_ba.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#ifndef ANDROID_INCLUDE_BT_BAP_BA_H +#define ANDROID_INCLUDE_BT_BAP_BA_H + +#include +#include "hardware/bt_av.h" + +__BEGIN_DECLS + +#define BT_PROFILE_BAP_BROADCAST_ID "bap_broadcast" + +/* Bluetooth BAP BROADCAST states */ +typedef enum { + BTBAP_BROADCAST_STATE_IDLE = 0, //Idle + BTBAP_BROADCAST_STATE_CONFIGURED, //Configured + BTBAP_BROADCAST_STATE_STREAMING, //Streaming +} btbap_broadcast_state_t; + +/* Bluetooth BAP BROADCAST Audio states */ +typedef enum { + BTBAP_BROADCAST_AUDIO_STATE_STOPPED = 0, + BTBAP_BROADCAST__AUDIO_STATE_STARTED, +} btbap_broadcast_audio_state_t; + +/** Callback for broadcast state change. + * state will have one of the values from btbap_broadcast_state_t + */ +typedef void (* bap_broadcast_state_callback)(int adv_id, + btbap_broadcast_state_t state); + +/** Callback for audiopath state change. + * state will have one of the values from btbap_broadcast_audio_state_t + */ +typedef void (* bap_broadcast_audio_state_callback)(int adv_id, + btbap_broadcast_audio_state_t state); + +/** Callback for audio configuration change. + */ +typedef void (* bap_broadcast_audio_config_callback)(int adv_id, + btav_a2dp_codec_config_t codec_config, + std::vector codec_capabilities); + +/** Callback for Iso datapath setup or removed. + */ +//typedef void (* bap_broadcast_iso_datapath_callback) (int big_handle, int enabled); + +/** Callback for encryption key generation notification + */ +typedef void (* bap_broadcast_enckey_callback) (std::string key); + +/** Callback to create/terminate BIG + */ + +typedef void (* bap_broadcast_setup_big_callback) (int enable, int adv_id, int big_handle, + int num_bises, std::vector bis_handles); + +typedef void (* bap_broadcast_bid_callback) (std::vector broadcast_id); + +/** BT-BAP-BROADCAST callback structure. */ +typedef struct { + /** set to sizeof(btbap_broadcast_callbacks_t) */ + size_t size; + bap_broadcast_state_callback broadcast_state_cb; + bap_broadcast_audio_state_callback audio_state_cb; + bap_broadcast_audio_config_callback audio_config_cb; + //bap_broadcast_iso_datapath_callback iso_datapath_cb; + bap_broadcast_enckey_callback enc_key_cb; + bap_broadcast_setup_big_callback create_big_cb; + bap_broadcast_bid_callback broadcast_id_cb; +} btbap_broadcast_callbacks_t; + +typedef struct { + + /** set to sizeof(btav_source_interface_t) */ + size_t size; + /** + * Register the btbap_broadcast callbacks. + */ + bt_status_t (*init)(btbap_broadcast_callbacks_t* callbacks, + int max_broadcast, btav_a2dp_codec_config_t config, int mode); + + /** Enable broadcast with provided codec config */ + bt_status_t (*enable_broadcast)(btav_a2dp_codec_config_t config); + + /** disable broadcast to move the state machine to idle state */ + bt_status_t (*disable_broadcast)(int adv_id); + + /** sets bap broadcast as active session */ + bt_status_t (*set_broadcast_active)(bool enable, uint8_t adv_id); + + /** configure the codecs settings preferences */ + bt_status_t (*codec_config_change)(uint8_t adv_id, btav_a2dp_codec_config_t config); + + /** Disable ISO datapath */ + bt_status_t (*setup_audiopath)(bool enable, uint8_t adv_id, uint8_t big_handle, int num_bises, int* bis_handles); + + /** Get stored encryption key */ + std::string (*get_encryption_key)( void ); + + /** Set Encryption with encryption lenght provided */ + bt_status_t (*set_encryption) (bool enabled, uint8_t key_length); + + /** Closes the interface. */ + void (*cleanup)( void ); + +} btbap_broadcast_interface_t; +__END_DECLS +#endif /*ANDROID_INCLUDE_BT_BAP_BA_H*/ + diff --git a/le_audio/vhal/include/hardware/bt_bap_uclient.h b/le_audio/vhal/include/hardware/bt_bap_uclient.h new file mode 100644 index 0000000000000000000000000000000000000000..7f5a481fc627bdff538fdf2c243e6aeb97bc492b --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_bap_uclient.h @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_INCLUDE_BT_BAP_UCLIENT_H +#define ANDROID_INCLUDE_BT_BAP_UCLIENT_H + +#include +#include +#include + + +namespace bluetooth { +namespace bap { +namespace ucast { + +#define BT_PROFILE_BAP_UCLIENT_ID "bt_bap_uclient" + +using bluetooth::bap::pacs::CodecConfig; + +constexpr uint8_t ASE_DIRECTION_SINK = 0x01 << 0; +constexpr uint8_t ASE_DIRECTION_SRC = 0x01 << 1; + +constexpr uint8_t LATENCY_LOW = 0x01; +constexpr uint8_t LATENCY_BALANCED = 0x02; +constexpr uint8_t LATENCY_HIGH = 0x03; + +// Content types +constexpr uint16_t CONTENT_TYPE_UNSPECIFIED = 0x0001; // Unspecified +constexpr uint16_t CONTENT_TYPE_CONVERSATIONAL = 0x0002; // Conversational + +// Media(music playback, radio, podcast or movie soundtrack, or tv audio) +constexpr uint16_t CONTENT_TYPE_MEDIA = 0x0004; +constexpr uint16_t CONTENT_TYPE_GAME = 0x0008; // Game Audio +constexpr uint16_t CONTENT_TYPE_INSTRUCTIONAL = 0x0010; // Instructional + +// ManMachine(with voice recognition or virtual assistants) +constexpr uint16_t CONTENT_TYPE_MAN_MACHINE = 0x0020; +constexpr uint16_t CONTENT_TYPE_LIVE = 0x0040; // Live audio + +// Sound Effects(including keyboard and touch feedback; +// menu and user interface sounds; and other system sounds) +constexpr uint16_t CONTENT_TYPE_SOUND_EFFECTS = 0x0080; + +// Notification and reminder sounds; attention-seeking audio, +//for example, in beeps signaling the arrival of a message +constexpr uint16_t CONTENT_TYPE_NOTIFICATIONS = 0x0100; +constexpr uint16_t CONTENT_TYPE_RINGTONE = 0x0200; // Ringtone +constexpr uint16_t CONTENT_TYPE_ALERT = 0x0400; // ImmediateAlert +constexpr uint16_t CONTENT_TYPE_EMERGENCY = 0x0800; // EmergencyAlert + +// Audio locations +constexpr uint32_t AUDIO_LOC_LEFT = 0x0001; +constexpr uint32_t AUDIO_LOC_RIGHT = 0x0002; +constexpr uint32_t AUDIO_LOC_CENTER = 0x0004; + +constexpr uint8_t LE_2M_PHY = 0x02; +constexpr uint8_t LE_QHS_PHY = 0x80; + +typedef uint8_t sdu_interval_t[3]; +typedef uint8_t presentation_delay_t[3]; +typedef uint8_t codec_type_t[5]; +typedef uint8_t codec_config[255]; + +struct CISConfig { + uint8_t cis_id; + uint16_t max_sdu_m_to_s; + uint16_t max_sdu_s_to_m; + uint8_t phy_m_to_s; + uint8_t phy_s_to_m; + uint8_t rtn_m_to_s; + uint8_t rtn_s_to_m; +}; + +struct CIGConfig { + uint8_t cig_id; + uint8_t cis_count; + uint8_t packing; + uint8_t framing; + uint16_t max_tport_latency_m_to_s; + uint16_t max_tport_latency_s_to_m; + sdu_interval_t sdu_interval_m_to_s; + sdu_interval_t sdu_interval_s_to_m; +}; + +struct ASCSConfig { + uint8_t cig_id; + uint8_t cis_id; + uint8_t target_latency; + bool bi_directional; + presentation_delay_t presentation_delay; +}; + +struct QosConfig { + CIGConfig cig_config; + std::vector cis_configs; // individual CIS configs + std::vector ascs_configs; +}; + +enum class AseState { + IDLE = 0, + CODEC_CONFIGURED, + QOS_CONFIGURED, + ENABLING, + STREAMING, + DISABLING, + RELEASING, +}; + +enum class StreamState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + STARTING, + STREAMING, + STOPPING, + DISCONNECTING, + RECONFIGURING, + UPDATING +}; + +enum class DeviceState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED +}; + +enum class StreamDiscReason { + REASON_NONE, + REASON_USER_DISC +}; + +struct CodecQosConfig { + CodecConfig codec_config; + QosConfig qos_config; +}; + +struct StreamType { + uint16_t type; + uint16_t audio_context; + uint8_t direction; +}; + +struct StreamConnect { + StreamType stream_type; + //CCID_List ccids; //TODO + std::vector codec_qos_config_pair; +}; + +enum class StreamReconfigType { + CODEC_CONFIG, + QOS_CONFIG +}; + +struct StreamReconfig { + StreamType stream_type; + StreamReconfigType reconf_type; + std::vector codec_qos_config_pair; +}; + +enum class StreamUpdateType { + STREAMING_CONTEXT, +}; + +struct StreamUpdate { + StreamType stream_type; + StreamUpdateType update_type; + uint16_t update_value; +}; + +struct StreamStateInfo { + StreamType stream_type; + StreamState stream_state; + StreamDiscReason reason; +}; + +struct StreamConfigInfo { + StreamType stream_type; + CodecConfig codec_config; // codec + uint32_t audio_location; // location info of remote dev for the stream + QosConfig qos_config; // current CIG, CISs configuration + std::vector codecs_selectable; // pacs codec capabilities +}; + +class UcastClientCallbacks { + public: + virtual ~UcastClientCallbacks() = default; + + virtual void OnStreamState(const RawAddress &address, + std::vector streams_state_info) = 0; + + virtual void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) = 0; + + virtual void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) = 0; +}; + +class UcastClientInterface { + public: + virtual ~UcastClientInterface() = default; + + /** Register the ucast client callbacks */ + virtual void Init(UcastClientCallbacks* callbacks) = 0; + + virtual void Connect(std::vector &address, bool is_direct, + std::vector &streams) = 0; + + virtual void Disconnect(const RawAddress& address, + std::vector &streams) = 0; + + virtual void Start(const RawAddress& address, + std::vector &streams) = 0; + + virtual void Stop(const RawAddress& address, + std::vector &streams) = 0; + + virtual void Reconfigure(const RawAddress& address, + std::vector &streams) = 0; + + virtual void UpdateStream(const RawAddress& address, + std::vector &update_streams) = 0; + + /** Closes the interface. */ + virtual void Cleanup() = 0; +}; + +UcastClientInterface* btif_bap_uclient_get_interface(); + +} // namespace ucast +} // namespace bap +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_BAP_UCLIENT_H */ diff --git a/le_audio/vhal/include/hardware/bt_csip.h b/le_audio/vhal/include/hardware/bt_csip.h new file mode 100644 index 0000000000000000000000000000000000000000..92eeef173a1d095ec49ed5e93fdc765c10892b7c --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_csip.h @@ -0,0 +1,119 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#ifndef ANDROID_INCLUDE_BT_CSIP_H +#define ANDROID_INCLUDE_BT_CSIP_H + +#include +#include +#include + +__BEGIN_DECLS + +#define BT_PROFILE_CSIP_CLIENT_ID "csip_client" + +/** Callback when app has registered with CSIP Client module + */ +typedef void (* btcsip_csip_app_registered_callback)(uint8_t status, uint8_t app_id, + const bluetooth::Uuid& app_uuid); + +/** Callback when connection state is changed for CSIP Profile + */ +typedef void (* btcsip_csip_connection_state_callback)(uint8_t app_id, RawAddress& bd_addr, + uint8_t state, uint8_t status); + +/** Callback when new set has been identified on remote device + */ +typedef void (* btcsip_new_set_found_callback) (uint8_t set_id, RawAddress& bd_addr, + uint8_t size, uint8_t* sirk, + const bluetooth::Uuid& p_srvc_uuid, + bool lock_support); + +/** Callback when new set member has been identified + */ +typedef void (* btcsip_new_set_member_found_callback) (uint8_t set_id, + RawAddress& bd_addr); + +/** Callback for lock status changed event to requesting client + */ +typedef void (* btcsip_lock_state_changed_callback) (uint8_t app_id, uint8_t set_id, + uint8_t value, uint8_t status, + std::vector addr); + +/** Callback when lock is available on earlier denying set member + */ +typedef void (* btcsip_lock_available_callback) (uint8_t app_id, uint8_t set_id, + RawAddress& bd_addr); + +/** Callback when size of coordinated set has been changed + */ +typedef void (* btcsip_set_size_changed_callback) (uint8_t set_id, uint8_t size, + RawAddress& bd_addr); + +/** Callback when SIRK of coordinated set has been changed + */ +typedef void (* btcsip_set_sirk_changed_callback) (uint8_t set_id, uint8_t* sirk, + RawAddress& bd_addr); + +/** BT-CSIP callback structure. */ +typedef struct { + size_t size; + btcsip_csip_app_registered_callback app_registered_cb; + btcsip_csip_connection_state_callback conn_state_cb; + btcsip_new_set_found_callback new_set_found_cb; + btcsip_new_set_member_found_callback new_set_member_cb; + btcsip_lock_state_changed_callback lock_status_cb; + btcsip_lock_available_callback lock_available_cb; + btcsip_set_size_changed_callback size_changed_cb; + btcsip_set_sirk_changed_callback sirk_changed_cb; +} btcsip_callbacks_t; + +/** Represents the standard BT-CSIP interface. */ +typedef struct { + + /** set to sizeof(BtCsipInterface) */ + size_t size; + + /** Register the BtCsipInterface callbacks + */ + bt_status_t (*init) (btcsip_callbacks_t* callbacks); + + /** CSIP opportunistic gatt client connection*/ + bt_status_t (*connect) (uint8_t app_id, RawAddress *bd_addr); + + /** disconnect csip gatt connection */ + bt_status_t (*disconnect) (uint8_t app_id, RawAddress *bd_addr ); + + /** register app/module with CSIP profile*/ + bt_status_t (*register_csip_app) (const bluetooth::Uuid& app_uuid); + + /** unregister app/module with CSIP profile */ + bt_status_t (*unregister_csip_app) (uint8_t app_id); + + /** change lock value */ + bt_status_t (*set_lock_value) (uint8_t app_id, uint8_t set_id, uint8_t lock_value, + std::vector devices); + + /** Closes the interface. */ + void (*cleanup) (void); + +} btcsip_interface_t; +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_CSIP_H */ diff --git a/le_audio/vhal/include/hardware/bt_mcp.h b/le_audio/vhal/include/hardware/bt_mcp.h new file mode 100644 index 0000000000000000000000000000000000000000..8bde52dece2e810ffbc8691f73098cf700aef513 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_mcp.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + + +#ifndef ANDROID_INCLUDE_BT_MCP_H +#define ANDROID_INCLUDE_BT_MCP_H + + + +#include +#include + +#define BT_PROFILE_MCP_ID "mcs_server" + +namespace bluetooth { +namespace mcp_server { + +class McpServerCallbacks { + public: + virtual ~McpServerCallbacks() = default; + virtual void OnConnectionStateChange(int status, const RawAddress& bd_addr) = 0; + virtual void MediaControlPointChangeReq(uint8_t state, const RawAddress& bd_addr) = 0; + virtual void TrackPositionChangeReq(int32_t position) = 0; + virtual void PlayingOrderChangeReq(uint32_t order) = 0; +}; + + +class McpServerInterface { + public: + virtual ~McpServerInterface() = default; + virtual void Init(McpServerCallbacks* callbacks, Uuid uuid) = 0; + virtual void MediaState(uint8_t state) = 0; + virtual void MediaPlayerName(uint8_t *name) = 0; + virtual void MediaControlPointOpcodeSupported(uint32_t feature) = 0; + virtual void MediaControlPoint(uint8_t value) = 0; + virtual void TrackChanged(bool status) = 0; + virtual void TrackTitle(uint8_t* title) = 0; + virtual void TrackPosition(int32_t position) = 0; + virtual void TrackDuration(int32_t duration) = 0; + virtual void PlayingOrderSupported(uint16_t order) = 0; + virtual void PlayingOrder(uint8_t value) = 0; + virtual void ContentControlId(uint8_t ccid) = 0; + virtual void SetActiveDevice(const RawAddress& address, int set_id, int profile) = 0; + virtual void DisconnectMcp(const RawAddress& address) = 0; + virtual void BondStateChange(const RawAddress& address, int state) = 0; + virtual void Cleanup(void) = 0; +}; + +} +} +#endif /* ANDROID_INCLUDE_BT_MCP_H */ diff --git a/le_audio/vhal/include/hardware/bt_pacs_client.h b/le_audio/vhal/include/hardware/bt_pacs_client.h new file mode 100644 index 0000000000000000000000000000000000000000..7579b728e3bb027d46da8fb8e45819ac7e60d757 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_pacs_client.h @@ -0,0 +1,183 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_INCLUDE_BT_PACS_CLIENT_H +#define ANDROID_INCLUDE_BT_PACS_CLIENT_H + +#include +#include + +namespace bluetooth { +namespace bap { +namespace pacs { + +#define BT_PROFILE_PACS_CLIENT_ID "bt_pacs_client" + +enum class CodecDirection { + CODEC_DIR_SINK = 0x1 << 0, + CODEC_DIR_SRC = 0x1 << 1 +}; + +enum class CodecCapFrameDuration { + FRAME_DUR_7_5 = 0x1 << 0, + FRAME_DUR_10 = 0x1 << 1, + FRAME_DUR_7_5_PREF = 0x1 << 4, + FRAME_DUR_10_PREF = 0x1 << 5, +}; + +enum class CodecFrameDuration { + FRAME_DUR_7_5 = 0x00, + FRAME_DUR_10 = 0x01, +}; + +enum class CodecCapChnlCount { + CHNL_COUNT_ONE = 0x1 << 0, + CHNL_COUNT_TWO = 0x1 << 1, + CHNL_COUNT_THREE = 0x1 << 2, + CHNL_COUNT_FOUR = 0x1 << 3, + CHNL_COUNT_FIVE = 0x1 << 4, + CHNL_COUNT_SIX = 0x1 << 5, + CHNL_COUNT_SEVEN = 0x1 << 6, + CHNL_COUNT_EIGHT = 0x1 << 7, +}; + +enum class ConnectionState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +enum class CodecIndex { + CODEC_INDEX_SOURCE_MIN = 0x09, + + // Add an entry for each source codec here. + // NOTE: The values should be same as those listed in the following file: + // BluetoothCodecConfig.java + CODEC_INDEX_SOURCE_LC3 = CODEC_INDEX_SOURCE_MIN, + CODEC_INDEX_SOURCE_MAX, + CODEC_INDEX_MIN = CODEC_INDEX_SOURCE_MIN, + CODEC_INDEX_MAX = CODEC_INDEX_SOURCE_MAX, +}; + +enum class CodecPriority { + // Disable the codec. + CODEC_PRIORITY_DISABLED = -1, + + // Reset the codec priority to its default value. + CODEC_PRIORITY_DEFAULT = 0, + + // Highest codec priority. + CODEC_PRIORITY_HIGHEST = 1000 * 1000 +}; + +enum class CodecSampleRate { + CODEC_SAMPLE_RATE_NONE = 0x0, + CODEC_SAMPLE_RATE_44100 = 0x1 << 0, + CODEC_SAMPLE_RATE_48000 = 0x1 << 1, + CODEC_SAMPLE_RATE_88200 = 0x1 << 2, + CODEC_SAMPLE_RATE_96000 = 0x1 << 3, + CODEC_SAMPLE_RATE_176400 = 0x1 << 4, + CODEC_SAMPLE_RATE_192000 = 0x1 << 5, + CODEC_SAMPLE_RATE_16000 = 0x1 << 6, + CODEC_SAMPLE_RATE_24000 = 0x1 << 7, + CODEC_SAMPLE_RATE_32000 = 0x1 << 8, + CODEC_SAMPLE_RATE_8000 = 0x1 << 9 +}; + +enum class CodecBPS { + CODEC_BITS_PER_SAMPLE_NONE = 0x0, + CODEC_BITS_PER_SAMPLE_16 = 0x1 << 0, + CODEC_BITS_PER_SAMPLE_24 = 0x1 << 1, + CODEC_BITS_PER_SAMPLE_32 = 0x1 << 2 +}; + +enum class CodecChannelMode { + CODEC_CHANNEL_MODE_NONE = 0x0, + CODEC_CHANNEL_MODE_MONO = 0x1 << 0, + CODEC_CHANNEL_MODE_STEREO = 0x1 << 1 +}; + +struct CodecConfig { + CodecIndex codec_type; + CodecPriority codec_priority; // Codec selection priority + // relative to other codecs: larger value + // means higher priority. If 0, reset to + // default. + CodecSampleRate sample_rate; + CodecBPS bits_per_sample; + CodecChannelMode channel_mode; + int64_t codec_specific_1; // Codec-specific value 1 + int64_t codec_specific_2; // Codec-specific value 2 + int64_t codec_specific_3; // Codec-specific value 3 + int64_t codec_specific_4; // Codec-specific value 4 +}; + +class PacsClientCallbacks { + public: + virtual ~PacsClientCallbacks() = default; + + /** Callback for pacs server registration status */ + virtual void OnInitialized(int status, int client_id) = 0; + + /** Callback for pacs server connection state change */ + virtual void OnConnectionState(const RawAddress& address, + ConnectionState state) = 0; + + /** Callback for audio ( media or voice) being available */ + virtual void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) = 0; + + /** Callback for pacs discovery results */ + virtual void OnSearchComplete(int status, + const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) = 0; +}; + +class PacsClientInterface { + public: + virtual ~PacsClientInterface() = default; + + /** Register the Pacs client callbacks */ + virtual void Init(PacsClientCallbacks* callbacks) = 0; + + /** Connect to pacs server */ + virtual void Connect(uint16_t client_id, const RawAddress& address) = 0; + + /** Disconnect pacs server */ + virtual void Disconnect(uint16_t client_id, const RawAddress& address) = 0; + + /** start pacs discovery */ + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + + /** get available audio context */ + virtual void GetAvailableAudioContexts(uint16_t client_id, + const RawAddress& address) = 0; + /** Closes the interface. */ + virtual void Cleanup(uint16_t client_id) = 0; +}; + +} // namespace pacs +} // namespace bap +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_CLIENT_H */ diff --git a/le_audio/vhal/include/hardware/bt_vcp_controller.h b/le_audio/vhal/include/hardware/bt_vcp_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..ea2e35a857058b3b7794dd441927ceb7ed606ca2 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_vcp_controller.h @@ -0,0 +1,82 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_INCLUDE_BT_VCP_CONTROLLER_H +#define ANDROID_INCLUDE_BT_VCP_CONTROLLER_H + +#include + +#define BT_PROFILE_VOLUME_CONTROL_ID "volume_control" + +namespace bluetooth { +namespace vcp_controller { + +enum class ConnectionState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +class VcpControllerCallbacks { + public: + virtual ~VcpControllerCallbacks() = default; + + /** Callback for profile connection state change */ + virtual void OnConnectionState(ConnectionState state, + const RawAddress& address) = 0; + + virtual void OnVolumeStateChange(uint8_t volume, uint8_t mute, + const RawAddress& address) = 0; + + virtual void OnVolumeFlagsChange(uint8_t flags, + const RawAddress& address) = 0; +}; + +class VcpControllerInterface { + public: + virtual ~VcpControllerInterface() = default; + + /** Register the Volume Controller callbacks */ + virtual void Init(VcpControllerCallbacks* callbacks) = 0; + + /** Connect to Volume Renderer device */ + virtual void Connect(const RawAddress& address, bool isDirect) = 0; + + /** Disconnect from Volume Renderer device */ + virtual void Disconnect(const RawAddress& address) = 0; + + /** Set absolute volume */ + virtual void SetAbsVolume(uint8_t volume, const RawAddress& address) = 0; + + virtual void Mute(const RawAddress& address) = 0; + + virtual void Unmute(const RawAddress& address) = 0; + + /** Closes the interface. */ + virtual void Cleanup(void) = 0; +}; + +} // namespace vcp_controller +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_VCP_CONTROLLER_H */ + +