mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	bluez: native-backend: Add call support in HFP HF
This commit implements the dial, answer and hangup callbacks to manage call when connected to HFP AG device. This adds +CLIP support to be able to get remote party phone number.
This commit is contained in:
		
							parent
							
								
									5dacd77866
								
							
						
					
					
						commit
						224f6a10f5
					
				
					 1 changed files with 157 additions and 1 deletions
				
			
		| 
						 | 
					@ -125,6 +125,7 @@ enum hfp_hf_state {
 | 
				
			||||||
	hfp_hf_cind1,
 | 
						hfp_hf_cind1,
 | 
				
			||||||
	hfp_hf_cind2,
 | 
						hfp_hf_cind2,
 | 
				
			||||||
	hfp_hf_cmer,
 | 
						hfp_hf_cmer,
 | 
				
			||||||
 | 
						hfp_hf_clip,
 | 
				
			||||||
	hfp_hf_slc1,
 | 
						hfp_hf_slc1,
 | 
				
			||||||
	hfp_hf_slc2,
 | 
						hfp_hf_slc2,
 | 
				
			||||||
	hfp_hf_vgs,
 | 
						hfp_hf_vgs,
 | 
				
			||||||
| 
						 | 
					@ -144,6 +145,12 @@ struct rfcomm_volume {
 | 
				
			||||||
	int hw_volume;
 | 
						int hw_volume;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct rfcomm_call_data {
 | 
				
			||||||
 | 
						struct rfcomm *rfcomm;
 | 
				
			||||||
 | 
						struct spa_bt_telephony_call *call;
 | 
				
			||||||
 | 
						struct spa_hook listener;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct rfcomm {
 | 
					struct rfcomm {
 | 
				
			||||||
	struct spa_list link;
 | 
						struct spa_list link;
 | 
				
			||||||
	struct spa_source source;
 | 
						struct spa_source source;
 | 
				
			||||||
| 
						 | 
					@ -175,6 +182,7 @@ struct rfcomm {
 | 
				
			||||||
	unsigned int codec;
 | 
						unsigned int codec;
 | 
				
			||||||
	uint32_t cind_enabled_indicators;
 | 
						uint32_t cind_enabled_indicators;
 | 
				
			||||||
	char *hf_indicators[MAX_HF_INDICATORS];
 | 
						char *hf_indicators[MAX_HF_INDICATORS];
 | 
				
			||||||
 | 
						struct spa_bt_telephony_ag *telephony_ag;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -269,6 +277,10 @@ static void volume_sync_stop_timer(struct rfcomm *rfcomm);
 | 
				
			||||||
static void rfcomm_free(struct rfcomm *rfcomm)
 | 
					static void rfcomm_free(struct rfcomm *rfcomm)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	codec_switch_stop_timer(rfcomm);
 | 
						codec_switch_stop_timer(rfcomm);
 | 
				
			||||||
 | 
						if (rfcomm->telephony_ag) {
 | 
				
			||||||
 | 
							telephony_ag_destroy(rfcomm->telephony_ag);
 | 
				
			||||||
 | 
							rfcomm->telephony_ag = NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	for (int i = 0; i < MAX_HF_INDICATORS; i++) {
 | 
						for (int i = 0; i < MAX_HF_INDICATORS; i++) {
 | 
				
			||||||
		if (rfcomm->hf_indicators[i]) {
 | 
							if (rfcomm->hf_indicators[i]) {
 | 
				
			||||||
			free(rfcomm->hf_indicators[i]);
 | 
								free(rfcomm->hf_indicators[i]);
 | 
				
			||||||
| 
						 | 
					@ -1223,10 +1235,87 @@ next_indicator:
 | 
				
			||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int hfp_hf_answer(void *data) {
 | 
				
			||||||
 | 
						struct rfcomm_call_data *call_data = telephony_call_get_user_data(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (call_data->call->state != CALL_STATE_INCOMING)
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rfcomm_send_cmd(call_data->rfcomm, "ATA");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int hfp_hf_hangup(void *data) {
 | 
				
			||||||
 | 
						struct rfcomm_call_data *call_data = telephony_call_get_user_data(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (call_data->call->state == CALL_STATE_INCOMING || call_data->call->state == CALL_STATE_ACTIVE) {
 | 
				
			||||||
 | 
							rfcomm_send_cmd(call_data->rfcomm, "AT+CHUP");
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct spa_bt_telephony_call_events telephony_call_events = {
 | 
				
			||||||
 | 
						SPA_VERSION_BT_TELEPHONY_AG_EVENTS,
 | 
				
			||||||
 | 
						.answer = hfp_hf_answer,
 | 
				
			||||||
 | 
						.hangup = hfp_hf_hangup,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct spa_bt_telephony_call *hfp_hf_add_call(struct rfcomm *rfcomm, struct spa_bt_telephony_ag *ag, enum spa_bt_telephony_call_state state,
 | 
				
			||||||
 | 
					                                                     const char *number)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct spa_bt_telephony_call *call;
 | 
				
			||||||
 | 
						struct rfcomm_call_data *data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						call = telephony_call_new(ag, sizeof(*data));
 | 
				
			||||||
 | 
						call->state = state;
 | 
				
			||||||
 | 
						if (number)
 | 
				
			||||||
 | 
							call->line_identification = strdup(number);
 | 
				
			||||||
 | 
						data = telephony_call_get_user_data(call);
 | 
				
			||||||
 | 
						data->rfcomm = rfcomm;
 | 
				
			||||||
 | 
						data->call = call;
 | 
				
			||||||
 | 
						telephony_call_add_listener(call, &data->listener, &telephony_call_events, call);
 | 
				
			||||||
 | 
						telephony_call_register(call);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return call;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int hfp_hf_dial(void *data, const char *number)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct rfcomm *rfcomm = data;
 | 
				
			||||||
 | 
						struct impl *backend = rfcomm->backend;
 | 
				
			||||||
 | 
						struct spa_bt_telephony_call *call;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						spa_log_info(backend->log, "Dialing: \"%s\"", number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, number);
 | 
				
			||||||
 | 
						if (!call)
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rfcomm_send_cmd(rfcomm, "ATD%s;", number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const struct spa_bt_telephony_ag_events telephony_ag_events = {
 | 
				
			||||||
 | 
						SPA_VERSION_BT_TELEPHONY_AG_EVENTS,
 | 
				
			||||||
 | 
						.dial = hfp_hf_dial,
 | 
				
			||||||
 | 
						.swap_calls = NULL,
 | 
				
			||||||
 | 
						.release_and_answer = NULL,
 | 
				
			||||||
 | 
						.release_and_swap = NULL,
 | 
				
			||||||
 | 
						.hold_and_answer = NULL,
 | 
				
			||||||
 | 
						.hangup_all = NULL,
 | 
				
			||||||
 | 
						.create_multiparty = NULL,
 | 
				
			||||||
 | 
						.send_tones = NULL
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
 | 
					static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct impl *backend = rfcomm->backend;
 | 
						struct impl *backend = rfcomm->backend;
 | 
				
			||||||
	unsigned int features, gain, selected_codec, indicator, value;
 | 
						unsigned int features, gain, selected_codec, indicator, value, type;
 | 
				
			||||||
 | 
						char number[17];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (sscanf(token, "+BRSF:%u", &features) == 1) {
 | 
						if (sscanf(token, "+BRSF:%u", &features) == 1) {
 | 
				
			||||||
		if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) &&
 | 
							if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) &&
 | 
				
			||||||
| 
						 | 
					@ -1304,6 +1393,64 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) {
 | 
								if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) {
 | 
				
			||||||
				spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5);
 | 
									spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5);
 | 
				
			||||||
 | 
								} else if (spa_streq(rfcomm->hf_indicators[indicator], "callsetup")) {
 | 
				
			||||||
 | 
									if (value == CIND_CALLSETUP_NONE) {
 | 
				
			||||||
 | 
										struct spa_bt_telephony_call *call, *tcall;
 | 
				
			||||||
 | 
										spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) {
 | 
				
			||||||
 | 
											if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING ||
 | 
				
			||||||
 | 
											    call->state == CALL_STATE_INCOMING) {
 | 
				
			||||||
 | 
												call->state = CALL_STATE_DISCONNECTED;
 | 
				
			||||||
 | 
												telephony_call_notify_updated_props(call);
 | 
				
			||||||
 | 
												telephony_call_destroy(call);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else if (value == CIND_CALLSETUP_INCOMING) {
 | 
				
			||||||
 | 
										spa_log_info(backend->log, "Incoming call");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_INCOMING, NULL) == NULL) {
 | 
				
			||||||
 | 
											spa_log_warn(backend->log, "failed to create incoming call");
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else if (value == CIND_CALLSETUP_ALERTING) {
 | 
				
			||||||
 | 
										struct spa_bt_telephony_call *call;
 | 
				
			||||||
 | 
										spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) {
 | 
				
			||||||
 | 
											if (call->state == CALL_STATE_DIALING) {
 | 
				
			||||||
 | 
												call->state = CALL_STATE_ALERTING;
 | 
				
			||||||
 | 
												telephony_call_notify_updated_props(call);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else if (spa_streq(rfcomm->hf_indicators[indicator], "call")) {
 | 
				
			||||||
 | 
									if (value == 0) {
 | 
				
			||||||
 | 
										struct spa_bt_telephony_call *call, *tcall;
 | 
				
			||||||
 | 
										spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) {
 | 
				
			||||||
 | 
											if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING ||
 | 
				
			||||||
 | 
											    call->state == CALL_STATE_INCOMING ||call->state == CALL_STATE_ACTIVE) {
 | 
				
			||||||
 | 
												call->state = CALL_STATE_DISCONNECTED;
 | 
				
			||||||
 | 
												telephony_call_notify_updated_props(call);
 | 
				
			||||||
 | 
												telephony_call_destroy(call);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else if (value == 1) {
 | 
				
			||||||
 | 
										struct spa_bt_telephony_call *call;
 | 
				
			||||||
 | 
										spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) {
 | 
				
			||||||
 | 
											if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING ||
 | 
				
			||||||
 | 
											    call->state == CALL_STATE_INCOMING) {
 | 
				
			||||||
 | 
												call->state = CALL_STATE_ACTIVE;
 | 
				
			||||||
 | 
												telephony_call_notify_updated_props(call);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2) {
 | 
				
			||||||
 | 
							struct spa_bt_telephony_call *call;
 | 
				
			||||||
 | 
							spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) {
 | 
				
			||||||
 | 
								if (call->state == CALL_STATE_INCOMING && !spa_streq(number, call->line_identification)) {
 | 
				
			||||||
 | 
									if (call->line_identification)
 | 
				
			||||||
 | 
										free(call->line_identification);
 | 
				
			||||||
 | 
									call->line_identification = strdup(number);
 | 
				
			||||||
 | 
									telephony_call_notify_updated_props(call);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else if (spa_strstartswith(token, "OK")) {
 | 
						} else if (spa_strstartswith(token, "OK")) {
 | 
				
			||||||
| 
						 | 
					@ -1340,6 +1487,10 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
 | 
				
			||||||
			rfcomm->hf_state = hfp_hf_cmer;
 | 
								rfcomm->hf_state = hfp_hf_cmer;
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case hfp_hf_cmer:
 | 
							case hfp_hf_cmer:
 | 
				
			||||||
 | 
								rfcomm_send_cmd(rfcomm, "AT+CLIP=1");
 | 
				
			||||||
 | 
								rfcomm->hf_state = hfp_hf_clip;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case hfp_hf_clip:
 | 
				
			||||||
			rfcomm->hf_state = hfp_hf_slc1;
 | 
								rfcomm->hf_state = hfp_hf_slc1;
 | 
				
			||||||
			rfcomm->slc_configured = true;
 | 
								rfcomm->slc_configured = true;
 | 
				
			||||||
			if (!rfcomm->codec_negotiation_supported) {
 | 
								if (!rfcomm->codec_negotiation_supported) {
 | 
				
			||||||
| 
						 | 
					@ -1353,6 +1504,11 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
 | 
				
			||||||
			/* Report volume on SLC establishment */
 | 
								/* Report volume on SLC establishment */
 | 
				
			||||||
			if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX))
 | 
								if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX))
 | 
				
			||||||
				rfcomm->hf_state = hfp_hf_vgs;
 | 
									rfcomm->hf_state = hfp_hf_vgs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								rfcomm->telephony_ag = telephony_ag_new(backend->telephony, sizeof(struct spa_hook));
 | 
				
			||||||
 | 
								telephony_ag_add_listener(rfcomm->telephony_ag, telephony_ag_get_user_data(rfcomm->telephony_ag),
 | 
				
			||||||
 | 
											  &telephony_ag_events, rfcomm);
 | 
				
			||||||
 | 
								telephony_ag_register(rfcomm->telephony_ag);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case hfp_hf_slc2:
 | 
							case hfp_hf_slc2:
 | 
				
			||||||
			if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX))
 | 
								if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue