diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index c2028ac..b5e84bf 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
#define HFP_HF_FEATURES ( \
HFP_HF_FEAT_ECNR | \
HFP_HF_FEAT_CLIP | \
+ HFP_HF_FEAT_ENHANCED_CALL_STATUS | \
HFP_HF_FEAT_ESCO_S4_T2 \
)
bool roaming;
uint8_t battchg;
+ bool session;
+ bool clcc_in_progress;
+
struct queue *calls;
+ struct queue *updated_calls;
char *dialing_number;
};
enum hfp_call_status status;
char *line_id;
uint type;
+ bool mpty;
struct hfp_hf *hfp;
};
hfp->event_handlers = queue_new();
hfp->cmd_queue = queue_new();
hfp->calls = queue_new();
+ hfp->updated_calls = queue_new();
hfp->writer_active = false;
if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp,
queue_destroy(hfp->calls, remove_call_cb);
hfp->calls = NULL;
+ queue_destroy(hfp->updated_calls, NULL);
+ hfp->updated_calls = NULL;
+
if (hfp->dialing_number) {
free(hfp->dialing_number);
hfp->dialing_number = NULL;
static struct hf_call *call_new(struct hfp_hf *hfp, unsigned int id,
enum hfp_call_status status,
- char *number)
+ char *number, unsigned int type,
+ bool mpty)
{
struct hf_call *call;
call->status = status;
if (number)
call->line_id = strdup(number);
+ call->type = type;
+ call->mpty = mpty;
call->hfp = hfp;
queue_push_tail(hfp->calls, call);
hfp->callbacks_data);
}
+static void clcc_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ const struct queue_entry *call_entry, *id_entry;
+ struct hf_call *call;
+ uint id;
+ bool found;
+ struct queue *to_remove;
+
+ DBG(hfp, "");
+
+ hfp->clcc_in_progress = false;
+
+ if (result != HFP_RESULT_OK) {
+ DBG(hfp, "hf: CLCC error: %d", result);
+ goto failed;
+ }
+
+ /* Removed disconnected calls */
+ to_remove = queue_new();
+ for (call_entry = queue_get_entries(hfp->calls); call_entry;
+ call_entry = call_entry->next) {
+ call = call_entry->data;
+ found = false;
+
+ for (id_entry = queue_get_entries(hfp->updated_calls);
+ id_entry; id_entry = id_entry->next) {
+ id = PTR_TO_UINT(id_entry->data);
+ if (call->id == id) {
+ found = true;
+ break;
+ }
+ }
+ DBG(hfp, "hf: call %d -> %s", call->id,
+ found ? "updated" : "disconnected");
+
+ if (!found)
+ queue_push_tail(to_remove, UINT_TO_PTR(call->id));
+ }
+
+ for (id_entry = queue_get_entries(to_remove);
+ id_entry; id_entry = id_entry->next) {
+ id = PTR_TO_UINT(id_entry->data);
+ call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id));
+ if (!call) {
+ DBG(hfp, "hf: Unknown call to remove: %u", id);
+ continue;
+ }
+ queue_remove(hfp->calls, call);
+ remove_call_cb(call);
+ }
+
+ queue_remove_all(hfp->updated_calls, NULL, NULL, NULL);
+ queue_destroy(to_remove, NULL);
+ return;
+
+failed:
+ if (!hfp->session && hfp->callbacks->session_ready)
+ hfp->callbacks->session_ready(result, cme_err,
+ hfp->callbacks_data);
+}
+
+static bool send_clcc(struct hfp_hf *hfp)
+{
+ if (!hfp->session || hfp->clcc_in_progress)
+ return true;
+
+ if (!hfp_hf_send_command(hfp, clcc_resp, hfp, "AT+CLCC")) {
+ DBG(hfp, "hf: Could not send AT+CLCC");
+ return false;
+ }
+
+ hfp->clcc_in_progress = true;
+
+ return true;
+}
+
static bool update_call_to_active(struct hfp_hf *hfp)
{
const struct queue_entry *entry;
DBG(hfp, "%u", val);
+ if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ send_clcc(hfp);
+ return;
+ }
+
if (val < hfp->ag_ind[HFP_INDICATOR_CALL].min ||
val > hfp->ag_ind[HFP_INDICATOR_CALL].max) {
DBG(hfp, "hf: Incorrect call state: %u", val);
DBG(hfp, "hf: No new call index available");
return;
}
- call_new(hfp, id, CALL_STATUS_ACTIVE, NULL);
+ call_new(hfp, id, CALL_STATUS_ACTIVE, NULL, 0, false);
}
break;
default:
DBG(hfp, "%u", val);
+ if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ send_clcc(hfp);
+ return;
+ }
+
if (val < hfp->ag_ind[HFP_INDICATOR_CALLSETUP].min ||
val > hfp->ag_ind[HFP_INDICATOR_CALLSETUP].max) {
DBG(hfp, "hf: Incorrect call setup state: %u", val);
DBG(hfp, "hf: No new call index available");
return;
}
- call_new(hfp, id, CALL_STATUS_INCOMING, NULL);
+ call_new(hfp, id, CALL_STATUS_INCOMING, NULL, 0, false);
break;
case CIND_CALLSETUP_DIALING:
case CIND_CALLSETUP_ALERTING:
DBG(hfp, "hf: No new call index available");
return;
}
- call_new(hfp, id, status, hfp->dialing_number);
+ call_new(hfp, id, status, hfp->dialing_number, 0, false);
if (hfp->dialing_number) {
free(hfp->dialing_number);
hfp->dialing_number = NULL;
DBG(hfp, "%u", val);
+ if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ send_clcc(hfp);
+ return;
+ }
+
if (val < hfp->ag_ind[HFP_INDICATOR_CALLHELD].min ||
val > hfp->ag_ind[HFP_INDICATOR_CALLHELD].max) {
DBG(hfp, "hf: Incorrect call held state: %u", val);
hfp->callbacks->update_operator(name, hfp->callbacks_data);
}
+static void clcc_cb(struct hfp_context *context, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ unsigned int id, status, mpty, type;
+ char number[255];
+ struct hf_call *call;
+
+ DBG(hfp, "");
+
+ if (!hfp_context_get_number(context, &id))
+ return;
+
+ /* Skip direction */
+ hfp_context_skip_field(context);
+
+ if (!hfp_context_get_number(context, &status))
+ return;
+
+ /* Skip mode */
+ hfp_context_skip_field(context);
+
+ if (!hfp_context_get_number(context, &mpty))
+ return;
+
+ if (!hfp_context_get_string(context, number, sizeof(number))) {
+ DBG(hfp, "hf: Could not get string");
+ return;
+ }
+
+ if (!hfp_context_get_number(context, &type))
+ return;
+
+ queue_push_tail(hfp->updated_calls, UINT_TO_PTR(id));
+
+ call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id));
+ if (!call) {
+ call_new(hfp, id, status, number, type, !!mpty);
+ return;
+ }
+
+ if (call->status != status) {
+ call->status = status;
+ if (hfp->callbacks && hfp->callbacks->call_status_updated)
+ hfp->callbacks->call_status_updated(call->id,
+ call->status, hfp->callbacks_data);
+ }
+
+ if (call->mpty != mpty) {
+ call->mpty = mpty;
+ if (hfp->callbacks && hfp->callbacks->call_mpty_updated)
+ hfp->callbacks->call_mpty_updated(call->id,
+ call->mpty, hfp->callbacks_data);
+ }
+
+ if (call->line_id && strcmp(call->line_id, number) == 0 &&
+ call->type == type)
+ return;
+
+ if (call->line_id)
+ free(call->line_id);
+ call->line_id = strdup(number);
+ call->type = type;
+
+ if (hfp->callbacks && hfp->callbacks->call_line_id_updated)
+ hfp->callbacks->call_line_id_updated(call->id,
+ call->line_id,
+ call->type,
+ hfp->callbacks_data);
+}
+
static void clip_cb(struct hfp_context *context, void *user_data)
{
struct hfp_hf *hfp = user_data;
goto failed;
}
+ hfp->session = true;
if (hfp->callbacks->session_ready)
hfp->callbacks->session_ready(HFP_RESULT_OK, 0,
hfp->callbacks_data);
+ if (hfp->features & HFP_AG_FEAT_ENHANCED_CALL_STATUS) {
+ if (!send_clcc(hfp)) {
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+ }
+
return;
failed:
if (hfp->features & HFP_AG_FEAT_IN_BAND_RING_TONE)
hfp_hf_register(hfp, bsir_cb, "+BSIR", hfp, NULL);
hfp_hf_register(hfp, ciev_cb, "+CIEV", hfp, NULL);
+ hfp_hf_register(hfp, clcc_cb, "+CLCC", hfp, NULL);
hfp_hf_register(hfp, clip_cb, "+CLIP", hfp, NULL);
hfp_hf_register(hfp, cops_cb, "+COPS", hfp, NULL);
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 27315bf..6e3d4c2 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
void *user_data);
void (*call_line_id_updated)(uint id, const char *number, uint type,
void *user_data);
+ void (*call_mpty_updated)(uint id, bool mpty, void *user_data);
};
struct hfp_hf *hfp_hf_new(int fd);
diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index c3f9ac4..b25b673 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
if (g_str_equal(test_name, "/HFP/HF/CIT/BV-01-C") ||
g_str_equal(test_name, "/HFP/HF/CLI/BV-01-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-01-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-02-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-03-C") ||
g_str_equal(test_name, "/HFP/HF/ICA/BV-04-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-04-C-full") ||
g_str_equal(test_name, "/HFP/HF/ICA/BV-06-C") ||
g_str_equal(test_name, "/HFP/HF/ICA/BV-07-C") ||
g_str_equal(test_name, "/HFP/HF/ICR/BV-01-C") ||
g_str_equal(test_name, "/HFP/HF/TCA/BV-02-C")) {
g_assert_cmpint(id, ==, 1);
g_assert_cmpint(status, ==, CALL_STATUS_INCOMING);
+ } else if (g_str_equal(test_name, "/HFP/HF/ICA/BV-01-C") ||
+ g_str_equal(test_name, "/HFP/HF/ICA/BV-02-C") ||
+ g_str_equal(test_name, "/HFP/HF/ICA/BV-03-C") ||
+ g_str_equal(test_name, "/HFP/HF/ICA/BV-04-C-full")) {
+ bool ret;
+
+ g_assert_cmpint(id, ==, 1);
+ g_assert_cmpint(status, ==, CALL_STATUS_INCOMING);
+ if (tester_use_debug())
+ tester_debug("call %d: answering call", id);
+ ret = hfp_hf_call_answer(context->hfp_hf, id, hf_cmd_complete,
+ context);
+ g_assert(ret);
} else if (g_str_equal(test_name, "/HFP/HF/OCL/BV-01-C")) {
const char *number;
g_assert_cmpstr(number, ==, str);
if (g_str_equal(test_name, "/HFP/HF/ENO/BV-01-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-01-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-02-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-03-C") ||
g_str_equal(test_name, "/HFP/HF/ICA/BV-04-C") ||
- g_str_equal(test_name, "/HFP/HF/ICA/BV-04-C-full") ||
g_str_equal(test_name, "/HFP/HF/ICA/BV-07-C") ||
g_str_equal(test_name, "/HFP/HF/TCA/BV-01-C") ||
g_str_equal(test_name, "/HFP/HF/TCA/BV-02-C")) {
define_hf_test("/HFP/HF/ENO/BV-01-C", test_hf_session,
NULL, test_hf_session_done,
FULL_SLC_SESSION('1', '0', '0', '0'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', 'R', 'I', 'N', 'G', '\r', '\n'),
'2', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '0', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
data_end());
/* Incoming call, in-band ring - HF */
define_hf_test("/HFP/HF/ICA/BV-01-C", test_hf_session,
NULL, test_hf_session_done,
FULL_SLC_SESSION('1', '0', '0', '0'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'B', 'S', 'I', 'R', ':', ' ',
'1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '1', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '4', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', 'R', 'I', 'N', 'G', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'L', 'I', 'P', ':',
'\"', '1', '2', '3', '4', '5', '6', '7', '\"',
'2', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '0', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '0', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'2', ',', '0', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
data_end());
/* Answer incoming call and accept in-band setting change - HF */
define_hf_test("/HFP/HF/ICA/BV-02-C", test_hf_session,
NULL, test_hf_session_done,
FULL_SLC_SESSION('1', '0', '0', '0'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'B', 'S', 'I', 'R', ':', ' ',
'0', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '1', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '4', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', 'R', 'I', 'N', 'G', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'L', 'I', 'P', ':',
'\"', '1', '2', '3', '4', '5', '6', '7', '\"',
'2', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '0', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '0', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'2', ',', '0', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'B', 'S', 'I', 'R', ':', ' ',
'1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '1', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '4', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', 'R', 'I', 'N', 'G', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'L', 'I', 'P', ':',
'\"', '1', '2', '3', '4', '5', '6', '7', '\"',
'2', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '0', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '0', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'2', ',', '0', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
data_end());
/* Answer incoming call on HF with ring muting - HF */
define_hf_test("/HFP/HF/ICA/BV-03-C", test_hf_session,
NULL, test_hf_session_done,
FULL_SLC_SESSION('1', '0', '0', '0'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'B', 'S', 'I', 'R', ':', ' ',
'0', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'B', 'S', 'I', 'R', ':', ' ',
'1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '1', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '4', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', 'R', 'I', 'N', 'G', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'L', 'I', 'P', ':',
'\"', '1', '2', '3', '4', '5', '6', '7', '\"',
'2', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '0', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '0', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'2', ',', '0', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
data_end());
/* Answer Incoming call on HF, no in-band ring - HF */
define_hf_test("/HFP/HF/ICA/BV-04-C-full", test_hf_session,
NULL, test_hf_session_done,
FULL_SLC_SESSION('1', '0', '0', '0'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'B', 'S', 'I', 'R', ':', ' ',
'0', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '1', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '4', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', 'R', 'I', 'N', 'G', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'L', 'I', 'P', ':',
'\"', '1', '2', '3', '4', '5', '6', '7', '\"',
'2', ',', '1', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'3', ',', '0', '\r', '\n'),
+ frg_pdu('\r', '\n', '+', 'C', 'L', 'C', 'C', ':', '1',
+ ',', '1', ',', '0', ',', '0', ',', '0', ',',
+ '\"', '1', '2', '3', '4', '5', '6', '7', '\"',
+ ',', '1', '2', '9', '\"', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
frg_pdu('\r', '\n', '+', 'C', 'I', 'E', 'V', ':', ' ',
'2', ',', '0', '\r', '\n'),
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n'),
data_end());
/* Answer Incoming call on AG, no in-band ring - HF */