Bluetooth: Classic: HFP_AG: Support ongoing calls before SLC

Support the case that there are some calls existed before SLC
established.

Add a callback to get the ongoing calls one by one from upper layer
when the response of the AT command `AT+CIND=?` from HF has been sent.

And set the Call, Call Setup, and Held Call indicators and report the
values int the response of AT command `AT+CIND?`. Then report all
ongoing calls in the `+CLCC` response.

Signed-off-by: Lyle Zhu <lyle.zhu@nxp.com>
This commit is contained in:
Lyle Zhu 2025-04-09 10:38:18 +08:00 committed by Benjamin Cabé
commit 2b4de08c7c
4 changed files with 652 additions and 263 deletions

View file

@ -77,6 +77,37 @@ enum hfp_ag_hf_indicators {
HFP_AG_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */
};
/* The status of the call */
enum __packed bt_hfp_ag_call_status {
BT_HFP_AG_CALL_STATUS_ACTIVE = 0, /* Call is active */
BT_HFP_AG_CALL_STATUS_HELD = 1, /* Call is on hold */
BT_HFP_AG_CALL_STATUS_DIALING = 2, /* Outgoing call is being dialed */
BT_HFP_AG_CALL_STATUS_ALERTING = 3, /* Outgoing call is being alerted */
BT_HFP_AG_CALL_STATUS_INCOMING = 4, /* Incoming call is came */
BT_HFP_AG_CALL_STATUS_WAITING = 5, /* Incoming call is waiting */
BT_HFP_AG_CALL_STATUS_INCOMING_HELD = 6 /* Call held by Response and Hold */
};
/* The direction of the call */
enum __packed bt_hfp_ag_call_dir {
BT_HFP_AG_CALL_DIR_OUTGOING = 0, /* It is a outgoing call */
BT_HFP_AG_CALL_DIR_INCOMING = 1, /* It is a incoming call */
};
/** @brief The ongoing call
*
* @param number Phone number terminated with '\0' of the call.
* @param type Specify the format of the phone number.
* @param dir Call direction.
* @param status The status of the call.
*/
struct bt_hfp_ag_ongoing_call {
char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1];
uint8_t type;
enum bt_hfp_ag_call_dir dir;
enum bt_hfp_ag_call_status status;
};
/** @brief HFP profile AG application callback */
struct bt_hfp_ag_cb {
/** HF AG connected callback to application
@ -116,6 +147,22 @@ struct bt_hfp_ag_cb {
*/
void (*sco_disconnected)(struct bt_conn *sco_conn, uint8_t reason);
/** Get ongoing call information Callback
*
* If this callback is provided it will be called whenever the response
* of the AT command `AT+CIND=?` from HF has been sent. It is used to get all
* ongoing calls one bye one from the upper layer.
* Then set the `Call`, `Call Setup`, and `Held Call` indicators and report the
* values int the response of AT command `AT+CIND?`. Then report all ongoing
* calls in the `+CLCC` response.
*
* @param ag HFP AG object.
* @param call Pointer to store the ongoing call information.
*
* @return 0 in case of success and will get next one or negative value in case of error.
*/
int (*get_ongoing_call)(struct bt_hfp_ag *ag, struct bt_hfp_ag_ongoing_call *call);
/** HF memory dialing request Callback
*
* If this callback is provided it will be called whenever a

View file

@ -279,8 +279,10 @@ static struct bt_hfp_ag_call *get_new_call(struct bt_hfp_ag *ag, const char *num
call = &ag->calls[index];
if (!atomic_test_and_set_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) {
/* Copy number to ag->number including null-character */
strcpy(call->number, number);
memset(call->number, 0, sizeof(call->number));
if (number != NULL) {
strncpy(call->number, number, sizeof(call->number) - 1);
}
call->type = type;
call->ag = ag;
k_work_init_delayable(&call->deferred_work, bt_ag_deferred_work);
@ -1016,6 +1018,147 @@ static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
return 0;
}
static void bt_hfp_ag_get_ongoing_calls(struct bt_hfp_ag *ag)
{
struct bt_hfp_ag_ongoing_call *call;
int err;
bool valid;
size_t len;
ag->ongoing_call_count = 0;
if ((bt_ag == NULL) || (bt_ag->get_ongoing_call == NULL)) {
LOG_DBG("No ongoing call retrieval method available");
return;
}
do {
call = &ag->ongoing_calls[ag->ongoing_call_count];
memset(call, 0, sizeof(*call));
valid = true;
err = bt_ag->get_ongoing_call(ag, call);
if (err != 0) {
LOG_DBG("No ongoing call retrieved");
break;
}
len = strlen(call->number);
if ((len == 0) || (len >= ARRAY_SIZE(call->number))) {
LOG_WRN("Invalid call number");
break;
}
switch (call->status) {
case BT_HFP_AG_CALL_STATUS_DIALING:
case BT_HFP_AG_CALL_STATUS_ALERTING:
if (call->dir == BT_HFP_AG_CALL_DIR_INCOMING) {
LOG_ERR("Dialing call cannot be incoming");
valid = false;
}
break;
case BT_HFP_AG_CALL_STATUS_INCOMING:
case BT_HFP_AG_CALL_STATUS_WAITING:
case BT_HFP_AG_CALL_STATUS_INCOMING_HELD:
if (call->dir == BT_HFP_AG_CALL_DIR_OUTGOING) {
LOG_ERR("Incoming call cannot be outgoing");
valid = false;
}
break;
default:
break;
}
if (!valid) {
continue;
}
ag->ongoing_call_count++;
} while (ag->ongoing_call_count < ARRAY_SIZE(ag->ongoing_calls));
if (ag->ongoing_call_count > 1) {
switch (ag->ongoing_calls[0].status) {
case BT_HFP_AG_CALL_STATUS_ACTIVE:
case BT_HFP_AG_CALL_STATUS_HELD:
case BT_HFP_AG_CALL_STATUS_INCOMING_HELD:
break;
default:
LOG_ERR("Unexpected call status for multiple calls");
ag->ongoing_call_count = 1;
break;
}
}
}
static int bt_hfp_ag_notify_cind_value(struct bt_hfp_ag *ag)
{
struct bt_hfp_ag_ongoing_call *call;
uint8_t call_value = 0;
uint8_t call_setup_value = 0;
uint8_t call_held_value = 0;
uint8_t call_active_count = 0;
uint8_t call_held_count = 0;
if (ag->ongoing_call_count == 0) {
return hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%u,%u,%u,%u,%u,%u,%u\r\n",
ag->indicator_value[BT_HFP_AG_SERVICE_IND],
ag->indicator_value[BT_HFP_AG_CALL_IND],
ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND],
ag->indicator_value[BT_HFP_AG_CALL_HELD_IND],
ag->indicator_value[BT_HFP_AG_SIGNAL_IND],
ag->indicator_value[BT_HFP_AG_ROAM_IND],
ag->indicator_value[BT_HFP_AG_BATTERY_IND]);
}
for (size_t index = 0; index < ag->ongoing_call_count; index++) {
call = &ag->ongoing_calls[index];
switch (call->status) {
case BT_HFP_AG_CALL_STATUS_ACTIVE:
call_value = 1;
call_active_count++;
break;
case BT_HFP_AG_CALL_STATUS_HELD:
call_value = 1;
call_held_count++;
break;
case BT_HFP_AG_CALL_STATUS_DIALING:
call_setup_value = BT_HFP_CALL_SETUP_OUTGOING;
break;
case BT_HFP_AG_CALL_STATUS_ALERTING:
call_setup_value = BT_HFP_CALL_SETUP_REMOTE_ALERTING;
break;
case BT_HFP_AG_CALL_STATUS_INCOMING:
call_setup_value = BT_HFP_CALL_SETUP_INCOMING;
break;
case BT_HFP_AG_CALL_STATUS_WAITING:
call_setup_value = BT_HFP_CALL_SETUP_INCOMING;
break;
case BT_HFP_AG_CALL_STATUS_INCOMING_HELD:
call_value = 1;
break;
}
}
if ((call_active_count != 0) && (call_held_count != 0)) {
call_held_value = BT_HFP_CALL_HELD_ACTIVE_HELD;
} else if (call_held_count != 0) {
call_held_value = BT_HFP_CALL_HELD_HELD;
} else {
call_held_value = BT_HFP_CALL_HELD_NONE;
}
return hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%u,%u,%u,%u,%u,%u,%u\r\n",
ag->indicator_value[BT_HFP_AG_SERVICE_IND],
call_value,
call_setup_value,
call_held_value,
ag->indicator_value[BT_HFP_AG_SIGNAL_IND],
ag->indicator_value[BT_HFP_AG_ROAM_IND],
ag->indicator_value[BT_HFP_AG_BATTERY_IND]);
}
static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
{
int err;
@ -1054,20 +1197,445 @@ static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
ag_ind[BT_HFP_AG_ROAM_IND].max, ag_ind[BT_HFP_AG_BATTERY_IND].name,
ag_ind[BT_HFP_AG_BATTERY_IND].min, ag_ind[BT_HFP_AG_BATTERY_IND].connector,
ag_ind[BT_HFP_AG_BATTERY_IND].max);
bt_hfp_ag_get_ongoing_calls(ag);
} else {
err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%d,%d,%d,%d,%d,%d,%d\r\n",
ag->indicator_value[BT_HFP_AG_SERVICE_IND],
ag->indicator_value[BT_HFP_AG_CALL_IND],
ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND],
ag->indicator_value[BT_HFP_AG_CALL_HELD_IND],
ag->indicator_value[BT_HFP_AG_SIGNAL_IND],
ag->indicator_value[BT_HFP_AG_ROAM_IND],
ag->indicator_value[BT_HFP_AG_BATTERY_IND]);
err = bt_hfp_ag_notify_cind_value(ag);
}
return err;
}
static void bt_hfp_ag_update_call_flags(struct bt_hfp_ag_call *call, enum bt_hfp_ag_call_dir dir)
{
int call_count;
call_count = get_none_released_calls(call->ag);
atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_INCOMING, dir == BT_HFP_AG_CALL_DIR_INCOMING);
if (call_count > 1) {
if (dir == BT_HFP_AG_CALL_DIR_INCOMING) {
atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY);
} else {
atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY);
}
}
}
static void bt_hfp_ag_notify_new_call(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call)
{
if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ||
atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) {
if (bt_ag && bt_ag->incoming) {
bt_ag->incoming(ag, call, call->number);
}
} else {
if (bt_ag && bt_ag->outgoing) {
bt_ag->outgoing(ag, call, call->number);
}
}
}
static void bt_hfp_ag_add_active_call(struct bt_hfp_ag *ag,
struct bt_hfp_ag_ongoing_call *ongoing_call)
{
struct bt_hfp_ag_call *call;
call = get_new_call(ag, ongoing_call->number, ongoing_call->type);
if (call == NULL) {
return;
}
LOG_DBG("Call %p is active", call);
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE);
bt_hfp_ag_update_call_flags(call, ongoing_call->dir);
bt_hfp_ag_notify_new_call(ag, call);
if (bt_ag && bt_ag->accept) {
bt_ag->accept(call);
}
}
static void bt_hfp_ag_add_held_call(struct bt_hfp_ag *ag,
struct bt_hfp_ag_ongoing_call *ongoing_call)
{
struct bt_hfp_ag_call *call;
call = get_new_call(ag, ongoing_call->number, ongoing_call->type);
if (call == NULL) {
return;
}
LOG_DBG("Call %p is held", call);
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD);
bt_hfp_ag_update_call_flags(call, ongoing_call->dir);
bt_hfp_ag_notify_new_call(ag, call);
if (bt_ag && bt_ag->held) {
bt_ag->held(call);
}
}
static void bt_hfp_ag_add_incoming_held_call(struct bt_hfp_ag *ag,
struct bt_hfp_ag_ongoing_call *ongoing_call)
{
struct bt_hfp_ag_call *call;
call = get_new_call(ag, ongoing_call->number, ongoing_call->type);
if (call == NULL) {
return;
}
LOG_DBG("Incoming call %p is held", call);
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE);
atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD);
bt_hfp_ag_update_call_flags(call, ongoing_call->dir);
bt_hfp_ag_notify_new_call(ag, call);
if (bt_ag && bt_ag->incoming_held) {
bt_ag->incoming_held(call);
}
}
static struct bt_hfp_ag_call *get_call_with_flag(struct bt_hfp_ag *ag, int bit)
{
struct bt_hfp_ag_call *call;
ARRAY_FOR_EACH(ag->calls, i) {
call = &ag->calls[i];
if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) {
continue;
}
if (atomic_test_bit(call->flags, bit)) {
return call;
}
}
return NULL;
}
static struct bt_hfp_ag_call *get_call_clear_flag(struct bt_hfp_ag *ag, int bit)
{
struct bt_hfp_ag_call *call;
call = get_call_with_flag(ag, bit);
if (call != NULL) {
atomic_clear_bit(call->flags, bit);
}
return call;
}
static struct bt_hfp_ag_call *get_call_with_flag_and_state(struct bt_hfp_ag *ag, int bit,
bt_hfp_call_state_t state)
{
struct bt_hfp_ag_call *call;
ARRAY_FOR_EACH(ag->calls, i) {
call = &ag->calls[i];
if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) {
continue;
}
if ((call->call_state & state) && atomic_test_bit(call->flags, bit)) {
return call;
}
}
return NULL;
}
static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data)
{
struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data;
if (call) {
ag_reject_call(call);
}
if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) {
LOG_DBG("It is not audio connection");
hfp_ag_close_sco(ag);
}
}
static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data)
{
int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE,
bt_hfp_ag_reject_cb, user_data);
if (err != 0) {
LOG_ERR("Fail to send err :(%d)", err);
}
}
static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag_call *call, bool in_bond)
{
if (bt_ag && bt_ag->ringing) {
bt_ag->ringing(call, in_bond);
}
}
static void hfp_ag_sco_connected(struct bt_sco_chan *chan)
{
struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan);
bt_hfp_call_state_t call_state;
struct bt_hfp_ag_call *call;
call = get_call_clear_flag(ag, BT_HFP_AG_CALL_OPEN_SCO);
if (call) {
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);
if ((call_state == BT_HFP_CALL_INCOMING) ||
atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) {
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING);
bt_hfp_ag_call_ringing_cb(call, true);
}
}
if ((bt_ag) && bt_ag->sco_connected) {
bt_ag->sco_connected(ag, chan->sco);
}
}
static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason)
{
struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan);
bt_hfp_call_state_t call_state;
struct bt_hfp_ag_call *call;
call = get_call_clear_flag(ag, BT_HFP_AG_CALL_OPEN_SCO);
if ((bt_ag != NULL) && bt_ag->sco_disconnected) {
bt_ag->sco_disconnected(chan->sco, reason);
}
if (!call) {
return;
}
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);
if ((call_state == BT_HFP_CALL_INCOMING) || (call_state == BT_HFP_CALL_OUTGOING)) {
bt_hfp_ag_call_reject(ag, call);
}
}
static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag)
{
struct bt_conn *sco_conn;
static struct bt_sco_chan_ops ops = {
.connected = hfp_ag_sco_connected,
.disconnected = hfp_ag_sco_disconnected,
};
LOG_DBG("");
if (ag->sco_chan.sco == NULL) {
ag->sco_chan.ops = &ops;
/* create SCO connection*/
sco_conn = bt_conn_create_sco(&ag->acl_conn->br.dst, &ag->sco_chan);
if (sco_conn != NULL) {
LOG_DBG("Created sco %p", sco_conn);
bt_conn_unref(sco_conn);
}
} else {
sco_conn = ag->sco_chan.sco;
}
return sco_conn;
}
static int hfp_ag_open_sco(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call)
{
bool create_sco;
bt_hfp_call_state_t call_state;
if (atomic_test_bit(ag->flags, BT_HFP_AG_CREATING_SCO)) {
LOG_WRN("SCO connection is creating!");
return 0;
}
hfp_ag_lock(ag);
create_sco = (ag->sco_chan.sco == NULL) ? true : false;
if (create_sco) {
atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO);
}
hfp_ag_unlock(ag);
if (create_sco) {
struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag);
atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO);
atomic_clear_bit(ag->flags, BT_HFP_AG_AUDIO_CONN);
if (sco_conn == NULL) {
LOG_ERR("Fail to create sco connection!");
return -ENOTCONN;
}
atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call == NULL);
if (call) {
atomic_set_bit(call->flags, BT_HFP_AG_CALL_OPEN_SCO);
}
LOG_DBG("SCO connection created (%p)", sco_conn);
} else {
if (call) {
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);
if ((call_state == BT_HFP_CALL_INCOMING) ||
atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) {
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING);
bt_hfp_ag_call_ringing_cb(call, true);
}
}
}
return 0;
}
static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag)
{
int err;
LOG_DBG("");
hfp_ag_lock(ag);
if (ag->selected_codec_id == 0) {
LOG_WRN("Codec is invalid, set default value");
ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD;
}
if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) {
LOG_ERR("Codec is unsupported (codec id %d)", ag->selected_codec_id);
hfp_ag_unlock(ag);
return -EINVAL;
}
hfp_ag_unlock(ag);
err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BCS:%d\r\n", ag->selected_codec_id);
if (err != 0) {
LOG_ERR("Fail to send err :(%d)", err);
}
return err;
}
static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call)
{
int err;
uint32_t hf_codec_ids;
hfp_ag_lock(ag);
hf_codec_ids = ag->hf_codec_ids;
hfp_ag_unlock(ag);
if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) {
err = bt_hfp_ag_codec_select(ag);
atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0);
if (call) {
atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_OPEN_SCO, err == 0);
}
} else {
err = hfp_ag_open_sco(ag, call);
}
return err;
}
static void bt_hfp_ag_notify_ongoing_calls(struct bt_hfp_ag *ag, void *user_data)
{
struct bt_hfp_ag_ongoing_call *ongoing_call;
struct bt_hfp_ag_call *call;
int err;
int active_call_count;
int held_call_count;
bool sco_created = false;
uint8_t call_setup_value = BT_HFP_CALL_SETUP_NONE;
for (size_t index = 0; index < ag->ongoing_call_count; index++) {
ongoing_call = &ag->ongoing_calls[index];
switch (ongoing_call->status) {
case BT_HFP_AG_CALL_STATUS_ACTIVE:
bt_hfp_ag_add_active_call(ag, ongoing_call);
break;
case BT_HFP_AG_CALL_STATUS_HELD:
bt_hfp_ag_add_held_call(ag, ongoing_call);
break;
case BT_HFP_AG_CALL_STATUS_DIALING:
case BT_HFP_AG_CALL_STATUS_ALERTING:
sco_created = true;
err = bt_hfp_ag_outgoing(ag, ongoing_call->number);
if (err != 0) {
sco_created = false;
LOG_ERR("Failed to initiate outgoing call: %d", err);
} else if (ongoing_call->status == BT_HFP_AG_CALL_STATUS_ALERTING) {
call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_IN_USING,
BT_HFP_CALL_OUTGOING);
if (call != NULL) {
atomic_set_bit(call->flags, BT_HFP_AG_CALL_ALERTING);
}
call_setup_value = BT_HFP_CALL_SETUP_REMOTE_ALERTING;
} else {
call_setup_value = BT_HFP_CALL_SETUP_OUTGOING;
}
break;
case BT_HFP_AG_CALL_STATUS_INCOMING:
case BT_HFP_AG_CALL_STATUS_WAITING:
sco_created = true;
err = bt_hfp_ag_remote_incoming(ag, ongoing_call->number);
if (err != 0) {
sco_created = false;
LOG_ERR("Failed to initiate remote incoming call: %d", err);
} else {
call_setup_value = BT_HFP_CALL_SETUP_INCOMING;
}
break;
case BT_HFP_AG_CALL_STATUS_INCOMING_HELD:
bt_hfp_ag_add_incoming_held_call(ag, ongoing_call);
break;
}
}
call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_IN_USING,
BT_HFP_CALL_ACTIVE | BT_HFP_CALL_HOLD);
if ((sco_created == false) && (call != NULL)) {
err = bt_hfp_ag_create_audio_connection(ag, call);
if (err) {
LOG_ERR("Failed to create audio connection: %d", err);
}
}
active_call_count = get_active_calls(ag);
held_call_count = get_held_calls(ag);
if ((active_call_count > 0) && (held_call_count > 0)) {
ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_ACTIVE_HELD;
} else if (held_call_count > 0) {
ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_HELD;
} else {
ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_NONE;
}
if ((active_call_count > 0) || (held_call_count > 0) ||
(get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD) != NULL)) {
ag->indicator_value[BT_HFP_AG_CALL_IND] = 1;
}
ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = call_setup_value;
}
static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data)
{
bool is_inband_ringtone;
@ -1079,6 +1647,8 @@ static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data)
atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0);
}
(void)hfp_ag_next_step(ag, bt_hfp_ag_notify_ongoing_calls, NULL);
}
static void bt_hfp_ag_slc_connected(struct bt_hfp_ag *ag, void *user_data)
@ -1705,29 +2275,6 @@ static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
return -ENOTSUP;
}
static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data)
{
struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data;
if (call) {
ag_reject_call(call);
}
if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) {
LOG_DBG("It is not audio connection");
hfp_ag_close_sco(ag);
}
}
static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data)
{
int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE,
bt_hfp_ag_reject_cb, user_data);
if (err != 0) {
LOG_ERR("Fail to send err :(%d)", err);
}
}
static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data)
{
struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data;
@ -1765,51 +2312,6 @@ static void bt_hfp_ag_call_terminate(struct bt_hfp_ag *ag, void *user_data)
}
}
struct bt_hfp_ag_call *get_call_with_flag(struct bt_hfp_ag *ag, int bit, bool clear)
{
struct bt_hfp_ag_call *call;
for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) {
call = &ag->calls[index];
if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) {
continue;
}
if (clear) {
if (atomic_test_and_set_bit(call->flags, bit)) {
return call;
}
} else {
if (atomic_test_bit(call->flags, bit)) {
return call;
}
}
}
return NULL;
}
struct bt_hfp_ag_call *get_call_with_flag_and_state(struct bt_hfp_ag *ag, int bit,
bt_hfp_call_state_t state)
{
struct bt_hfp_ag_call *call;
for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) {
call = &ag->calls[index];
if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) {
continue;
}
if ((call->call_state & state) && atomic_test_bit(call->flags, bit)) {
return call;
}
}
return NULL;
}
static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
{
int err;
@ -1826,7 +2328,7 @@ static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
if (call_count == 1) {
bt_hfp_ag_tx_cb_t next_step = NULL;
call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING, false);
call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING);
if (!call) {
return 0;
}
@ -1885,7 +2387,11 @@ static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call)
}
break;
case BT_HFP_CALL_OUTGOING:
status = BT_HFP_CLCC_STATUS_DIALING;
if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) {
status = BT_HFP_CLCC_STATUS_ALERTING;
} else {
status = BT_HFP_CLCC_STATUS_DIALING;
}
break;
case BT_HFP_CALL_INCOMING:
status = BT_HFP_CLCC_STATUS_INCOMING;
@ -1938,7 +2444,8 @@ static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
continue;
}
dir = atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ? 1 : 0;
dir = atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ?
BT_HFP_CLCC_DIR_INCOMING : BT_HFP_CLCC_DIR_OUTGOING;
status = bt_hfp_get_call_state(call);
mode = 0;
mpty = (status == BT_HFP_CLCC_STATUS_ACTIVE) && (active_calls > 1) ? 1 : 0;
@ -2003,185 +2510,6 @@ static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
return 0;
}
static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag_call *call, bool in_bond)
{
if (bt_ag && bt_ag->ringing) {
bt_ag->ringing(call, in_bond);
}
}
static void hfp_ag_sco_connected(struct bt_sco_chan *chan)
{
struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan);
bt_hfp_call_state_t call_state;
struct bt_hfp_ag_call *call;
call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, true);
if (call) {
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);
if (call_state == BT_HFP_CALL_INCOMING) {
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING);
bt_hfp_ag_call_ringing_cb(call, true);
}
}
if ((bt_ag) && bt_ag->sco_connected) {
bt_ag->sco_connected(ag, chan->sco);
}
}
static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason)
{
struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan);
bt_hfp_call_state_t call_state;
struct bt_hfp_ag_call *call;
call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, true);
if ((bt_ag != NULL) && bt_ag->sco_disconnected) {
bt_ag->sco_disconnected(chan->sco, reason);
}
if (!call) {
return;
}
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);
if ((call_state == BT_HFP_CALL_INCOMING) || (call_state == BT_HFP_CALL_OUTGOING)) {
bt_hfp_ag_call_reject(ag, call);
}
}
static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag)
{
struct bt_conn *sco_conn;
static struct bt_sco_chan_ops ops = {
.connected = hfp_ag_sco_connected,
.disconnected = hfp_ag_sco_disconnected,
};
LOG_DBG("");
if (ag->sco_chan.sco == NULL) {
ag->sco_chan.ops = &ops;
/* create SCO connection*/
sco_conn = bt_conn_create_sco(&ag->acl_conn->br.dst, &ag->sco_chan);
if (sco_conn != NULL) {
LOG_DBG("Created sco %p", sco_conn);
bt_conn_unref(sco_conn);
}
} else {
sco_conn = ag->sco_chan.sco;
}
return sco_conn;
}
static int hfp_ag_open_sco(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call)
{
bool create_sco;
bt_hfp_call_state_t call_state;
if (atomic_test_bit(ag->flags, BT_HFP_AG_CREATING_SCO)) {
LOG_WRN("SCO connection is creating!");
return 0;
}
hfp_ag_lock(ag);
create_sco = (ag->sco_chan.sco == NULL) ? true : false;
if (create_sco) {
atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO);
}
hfp_ag_unlock(ag);
if (create_sco) {
struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag);
atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO);
atomic_clear_bit(ag->flags, BT_HFP_AG_AUDIO_CONN);
if (sco_conn == NULL) {
LOG_ERR("Fail to create sco connection!");
return -ENOTCONN;
}
atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call == NULL);
if (call) {
atomic_set_bit(call->flags, BT_HFP_AG_CALL_OPEN_SCO);
}
LOG_DBG("SCO connection created (%p)", sco_conn);
} else {
if (call) {
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);
if (call_state == BT_HFP_CALL_INCOMING) {
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING);
bt_hfp_ag_call_ringing_cb(call, true);
}
}
}
return 0;
}
static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag)
{
int err;
LOG_DBG("");
hfp_ag_lock(ag);
if (ag->selected_codec_id == 0) {
LOG_WRN("Codec is invalid, set default value");
ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD;
}
if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) {
LOG_ERR("Codec is unsupported (codec id %d)", ag->selected_codec_id);
hfp_ag_unlock(ag);
return -EINVAL;
}
hfp_ag_unlock(ag);
err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BCS:%d\r\n", ag->selected_codec_id);
if (err != 0) {
LOG_ERR("Fail to send err :(%d)", err);
}
return err;
}
static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call)
{
int err;
uint32_t hf_codec_ids;
hfp_ag_lock(ag);
hf_codec_ids = ag->hf_codec_ids;
hfp_ag_unlock(ag);
if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) {
err = bt_hfp_ag_codec_select(ag);
atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0);
if (call) {
atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_OPEN_SCO, err == 0);
}
} else {
err = hfp_ag_open_sco(ag, call);
}
return err;
}
static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data)
{
int err;
@ -2244,7 +2572,7 @@ static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
return -ENOTSUP;
}
call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING, false);
call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING);
__ASSERT(call, "Invalid call object");
if (call->call_state != BT_HFP_CALL_ALERTING) {
@ -2394,7 +2722,7 @@ static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
codec_conn = atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN);
atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED);
call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, false);
call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO);
if (err == 0) {
if (codec_conn && bt_ag && bt_ag->codec_negotiate) {
@ -2439,6 +2767,9 @@ static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data)
if (err) {
bt_hfp_ag_call_reject(ag, user_data);
}
} else if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) {
bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING);
bt_hfp_ag_call_ringing_cb(call, false);
}
}
@ -2821,7 +3152,7 @@ static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
struct bt_hfp_ag_call *call;
if (is_char(buf, '?')) {
call = get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD, false);
call = get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD);
hfp_ag_lock(ag);
call_state = call->call_state;
hfp_ag_unlock(ag);

View file

@ -149,6 +149,7 @@ enum {
BT_HFP_AG_CALL_OPEN_SCO, /* Open SCO */
BT_HFP_AG_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */
BT_HFP_AG_CALL_INCOMING_3WAY, /* Incoming 3 way call */
BT_HFP_AG_CALL_ALERTING, /* Pending for alerting */
/* Total number of flags - must be at the end of the enum */
BT_HFP_AG_CALL_NUM_FLAGS,
@ -235,6 +236,10 @@ struct bt_hfp_ag {
/* calls */
struct bt_hfp_ag_call calls[CONFIG_BT_HFP_AG_MAX_CALLS];
/* ongoing calls */
struct bt_hfp_ag_ongoing_call ongoing_calls[CONFIG_BT_HFP_AG_MAX_CALLS];
size_t ongoing_call_count;
/* last dialing number and type */
char last_number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1];
uint8_t type;

View file

@ -136,6 +136,12 @@
/* Call held by Response and Hold */
#define BT_HFP_CLCC_STATUS_CALL_HELD_HOLD 6
/* CLCC direction */
/* CLCC direction: incoming */
#define BT_HFP_CLCC_DIR_INCOMING 1
/* CLCC direction: outgoing */
#define BT_HFP_CLCC_DIR_OUTGOING 0
/* BVRA Value */
/* BVRA Deactivation */
#define BT_HFP_BVRA_DEACTIVATION 0