diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index f94df90..29b467a 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
+#include <limits.h>
#include "src/shared/util.h"
#include "src/shared/ringbuf.h"
hfp_debug(_hfp->debug_callback, _hfp->debug_data, "%s:%s() " fmt, \
__FILE__, __func__, ## arg)
-#define HFP_HF_FEATURES (HFP_HF_FEAT_ESCO_S4_T2)
+#define HFP_HF_FEATURES (HFP_HF_FEAT_CLIP | HFP_HF_FEAT_ESCO_S4_T2)
struct hfp_gw {
int ref_count;
bool roaming;
uint8_t battchg;
+ struct queue *calls;
};
struct cmd_handler {
hfp_hf_result_func_t callback;
};
+struct hf_call {
+ uint id;
+ enum hfp_call_status status;
+ char *line_id;
+ uint type;
+
+ struct hfp_hf *hfp;
+};
+
static void hfp_debug(hfp_debug_func_t debug_func, void *debug_data,
const char *format, ...)
{
hfp->event_handlers = queue_new();
hfp->cmd_queue = queue_new();
+ hfp->calls = queue_new();
hfp->writer_active = false;
if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp,
return hfp;
}
+static void remove_call_cb(void *user_data)
+{
+ struct hf_call *call = user_data;
+ struct hfp_hf *hfp = call->hfp;
+
+ if (hfp->callbacks && hfp->callbacks->call_removed)
+ hfp->callbacks->call_removed(call->id, hfp->callbacks_data);
+
+ free(call->line_id);
+ free(call);
+}
+
void hfp_hf_unref(struct hfp_hf *hfp)
{
if (!hfp)
queue_destroy(hfp->cmd_queue, free);
hfp->cmd_queue = NULL;
+ queue_destroy(hfp->calls, remove_call_cb);
+ hfp->calls = NULL;
+
if (!hfp->in_disconnect) {
free(hfp);
return;
return io_shutdown(hfp->io);
}
+static bool call_id_match(const void *data, const void *match_data)
+{
+ const struct hf_call *call = data;
+ uint id = PTR_TO_UINT(match_data);
+
+ return (call->id == id);
+}
+
+static uint next_call_index(struct hfp_hf *hfp)
+{
+ for (uint i = 1; i < UINT_MAX; i++) {
+ if (!queue_find(hfp->calls, call_id_match, UINT_TO_PTR(i)))
+ return i;
+ }
+
+ return 0;
+}
+
+static struct hf_call *call_new(struct hfp_hf *hfp, unsigned int id,
+ enum hfp_call_status status,
+ char *number)
+{
+ struct hf_call *call;
+
+ call = new0(struct hf_call, 1);
+ call->id = id;
+ call->status = status;
+ call->line_id = number;
+ call->hfp = hfp;
+ queue_push_tail(hfp->calls, call);
+
+ if (hfp->callbacks && hfp->callbacks->call_added)
+ hfp->callbacks->call_added(call->id, call->status,
+ hfp->callbacks_data);
+
+ return call;
+}
+
static void ciev_service_cb(uint8_t val, void *user_data)
{
struct hfp_hf *hfp = user_data;
}
}
+static bool call_outgoing_match(const void *data, const void *match_data)
+{
+ const struct hf_call *call = data;
+
+ return (call->status == CALL_STATUS_DIALING ||
+ call->status == CALL_STATUS_ALERTING);
+}
+
+static bool call_incoming_match(const void *data, const void *match_data)
+{
+ const struct hf_call *call = data;
+
+ return (call->status == CALL_STATUS_INCOMING);
+}
+
+static bool call_setup_match(const void *data, const void *match_data)
+{
+ return (call_outgoing_match(data, match_data) ||
+ call_incoming_match(data, match_data));
+}
+
+static bool call_active_match(const void *data, const void *match_data)
+{
+ const struct hf_call *call = data;
+
+ return (call->status == CALL_STATUS_ACTIVE);
+}
+
static void ciev_callsetup_cb(uint8_t val, void *user_data)
{
struct hfp_hf *hfp = user_data;
+ struct hf_call *call;
+ uint id;
+ enum hfp_call_status status;
DBG(hfp, "%u", val);
DBG(hfp, "hf: Incorrect call setup state: %u", val);
return;
}
+
+ switch (val) {
+ case CIND_CALLSETUP_NONE:
+ /* remove call in setup phase */
+ queue_remove_all(hfp->calls, call_setup_match, hfp,
+ remove_call_cb);
+ break;
+ case CIND_CALLSETUP_INCOMING:
+ if (queue_length(hfp->calls) != 0) {
+ DBG(hfp, "hf: Call already exists");
+ return;
+ }
+
+ id = next_call_index(hfp);
+ if (id == 0) {
+ DBG(hfp, "hf: No new call index available");
+ return;
+ }
+ call_new(hfp, id, CALL_STATUS_INCOMING, NULL);
+ break;
+ case CIND_CALLSETUP_DIALING:
+ case CIND_CALLSETUP_ALERTING:
+ if (val == CIND_CALLSETUP_DIALING)
+ status = CALL_STATUS_DIALING;
+ else
+ status = CALL_STATUS_ALERTING;
+
+ if (queue_find(hfp->calls, call_active_match, NULL)) {
+ DBG(hfp, "hf: Error: active call");
+ return;
+ }
+
+ call = queue_find(hfp->calls, call_outgoing_match, NULL);
+ if (call && 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);
+ return;
+ }
+
+ id = next_call_index(hfp);
+ if (id == 0) {
+ DBG(hfp, "hf: No new call index available");
+ return;
+ }
+ call_new(hfp, id, status, NULL);
+ break;
+ }
}
static void ciev_callheld_cb(uint8_t val, void *user_data)
hfp->callbacks->update_operator(name, hfp->callbacks_data);
}
-static void cops_resp(enum hfp_result result, enum hfp_error cme_err,
+static void clip_cb(struct hfp_context *context, void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+ char number[255];
+ unsigned int type;
+ struct hf_call *call;
+
+ DBG(hfp, "");
+
+ 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;
+
+ call = queue_find(hfp->calls, call_incoming_match, NULL);
+ if (!call) {
+ DBG(hfp, "hf: no incoming call");
+ return;
+ }
+
+ 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_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
struct hfp_hf *hfp = user_data;
DBG(hfp, "");
if (result != HFP_RESULT_OK) {
- DBG(hfp, "hf: COPS? error: %d", result);
+ DBG(hfp, "hf: CLIP error: %d", result);
goto failed;
}
hfp->callbacks_data);
}
+static void cops_resp(enum hfp_result result, enum hfp_error cme_err,
+ void *user_data)
+{
+ struct hfp_hf *hfp = user_data;
+
+ DBG(hfp, "");
+
+ if (result != HFP_RESULT_OK) {
+ DBG(hfp, "hf: COPS? error: %d", result);
+ goto failed;
+ }
+
+ /* SLC creation done, continue with default setup */
+ if (!hfp_hf_send_command(hfp, clip_resp, hfp,
+ "AT+CLIP=1")) {
+ DBG(hfp, "hf: Could not send AT+CLIP=1");
+ result = HFP_RESULT_ERROR;
+ goto failed;
+ }
+
+ return;
+
+failed:
+ if (hfp->callbacks->session_ready)
+ hfp->callbacks->session_ready(result, cme_err,
+ hfp->callbacks_data);
+}
+
static void cops_conf_resp(enum hfp_result result, enum hfp_error cme_err,
void *user_data)
{
/* Register unsolicited results handlers */
hfp_hf_register(hfp, ciev_cb, "+CIEV", hfp, NULL);
+ hfp_hf_register(hfp, clip_cb, "+CLIP", hfp, NULL);
hfp_hf_register(hfp, cops_cb, "+COPS", hfp, NULL);
return;
return hfp_hf_send_command(hfp, slc_brsf_resp, hfp,
"AT+BRSF=%u", HFP_HF_FEATURES);
}
+
+const char *hfp_hf_call_get_number(struct hfp_hf *hfp, uint id)
+{
+ struct hf_call *call;
+
+ DBG(hfp, "");
+
+ if (!hfp)
+ return false;
+
+ call = queue_find(hfp->calls, call_id_match, UINT_TO_PTR(id));
+ if (!call) {
+ DBG(hfp, "hf: no call with id: %u", id);
+ return false;
+ }
+
+ return call->line_id;
+}
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 27f6d2d..fec63c1 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
CIND_CALLHELD_HOLD
};
+enum hfp_call_status {
+ CALL_STATUS_ACTIVE = 0,
+ CALL_STATUS_HELD,
+ CALL_STATUS_DIALING,
+ CALL_STATUS_ALERTING,
+ CALL_STATUS_INCOMING,
+ CALL_STATUS_WAITING,
+ CALL_STATUS_RESPONSE_AND_HOLD
+};
+
struct hfp_context;
typedef void (*hfp_result_func_t)(struct hfp_context *context,
void (*update_indicator)(enum hfp_indicator indicator, uint32_t val,
void *user_data);
void (*update_operator)(const char *operator_name, void *user_data);
+
+ void (*call_added)(uint id, enum hfp_call_status status,
+ void *user_data);
+ void (*call_removed)(uint id, void *user_data);
+ void (*call_status_updated)(uint id, enum hfp_call_status status,
+ void *user_data);
+ void (*call_line_id_updated)(uint id, const char *number, uint type,
+ void *user_data);
};
struct hfp_hf *hfp_hf_new(int fd);
struct hfp_hf_callbacks *callbacks,
void *callbacks_data);
bool hfp_hf_session(struct hfp_hf *hfp);
+
+const char *hfp_hf_call_get_number(struct hfp_hf *hfp, uint id);
diff --git a/unit/test-hfp.c b/unit/test-hfp.c
index 8ab6c7b..f22a687 100644
--- a/unit/test-hfp.c
+++ b/unit/test-hfp.c
raw_pdu('\r', '\n', '+', 'C', 'O', 'P', 'S', ':', ' '), \
frg_pdu('0', ',', '0', ',', '\"', 'T', 'E', 'S', 'T'), \
frg_pdu('\"', '\r', '\n'), \
- frg_pdu('\r', '\n', 'O', 'K', '\r', '\n')
+ frg_pdu('\r', '\n', 'O', 'K', '\r', '\n'), \
+ raw_pdu('\r', '\n', 'O', 'K', '\r', '\n')
static void hf_session_ready_cb(enum hfp_result res, enum hfp_error cme_err,
void *user_data)