1、定义
蓝牙电话控制协议(Hands-Free Profile,HFP):HFP定义了蓝牙设备之间进行电话通信的基本流程和命令。 让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等。HFP中蓝牙两端的数据交互是通过定义好的AT指令来通讯。
HFP协议定义了音频网关(AG)和免提组件(HF)两个角色:
- 音频网关(Audio Gateway) :该设备为音频输入/输出的网关 。典型作为网关的设备为手机。
- 免提组件(Hand-Free) :该设备作为音频网关的远程音频输入/输出机制,并可提供若干遥控功能。典型作为免提组件的设备为车机、蓝牙耳机。
2、HFP连接流程
L2CAP 连接:首先,客户端设备和 HFP 设备需要通过 L2CAP(Logical Link Control and Adaptation Protocol)协议建立连接。L2CAP 是蓝牙协议栈中的一个底层协议,用于提供透明数据通信服务。在 HFP 连接过程中,L2CAP 协议主要用于数据的传输和控制。
SDP 查找服务:在 L2CAP 连接建立之后,客户端设备需要通过 SDP(Service Discovery Protocol)协议查找 HFP 设备的服务信息。SDP 协议是蓝牙协议栈中的一个服务发现协议,用于在蓝牙设备之间发现可用的服务。通过 SDP 查找服务,客户端设备可以确定要连接的 HFP 设备是否支持 HFP 协议,并获取 HFP 设备的服务信息。
RFCOMM 连接:在确认 HFP 设备支持 HFP 协议后,客户端设备需要通过 RFCOMM(Radio Frequency Communication)协议建立连接。RFCOMM 协议是蓝牙协议栈中的一个串口模拟协议,用于在蓝牙设备之间建立虚拟串口连接。在 HFP 连接过程中,RFCOMM 协议主要用于 HFP 协议栈与蓝牙协议栈之间的数据交换。
HFP 协议连接:在 RFCOMM 连接建立之后,客户端设备和 HFP 设备之间就可以开始进行 HFP 协议连接了。HFP 协议栈是蓝牙协议栈中实现 HFP 协议的核心组成部分,它负责处理与 HFP 相关的音频编解码、通话控制、音量控制等功能,保证了蓝牙设备之间的语音通信质量和流畅度。通过 HFP 协议栈,客户端设备和 HFP 设备之间可以进行通话管理和音频传输等功能。
3、获取HFP协议对象
public class BluetoothHeadsetUtil {
private static final String TAG = "BluetoothHeadsetClientUtil";
private static BluetoothHeadsetClient mBluetoothHeadsetClient;
private static final int BLUETOOTH_PROFILE_HEADSET_CLIENT = 16;
public static void getProfileProxy(Context mContext) {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BLUETOOTH_PROFILE_HEADSET_CLIENT);
}
private static final BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.i(TAG, "onServiceConnected: " + profile);
if (profile == BLUETOOTH_PROFILE_HEADSET_CLIENT) {
mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
}
}
@Override
public void onServiceDisconnected(int profile) {
Log.i(TAG, "onServiceDisconnected: " + profile);
mBluetoothHeadsetClient = null;
}
};
}
4、HFP 相关API
/**
* 判断设备是否已连接
*
* @param device 蓝牙设备
* @return bool true: 已连接
*/
public static boolean isDeviceConnected(BluetoothDevice device) {
if (mBluetoothHeadsetClient != null) {
return mBluetoothHeadsetClient.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED;
}
return false;
}
/**
* 获取已连接设备
*
* @return BluetoothDevice
*/
public static BluetoothDevice getConnectedDevice() {
if (mBluetoothHeadsetClient != null) {
List<BluetoothDevice> deviceList = mBluetoothHeadsetClient.getConnectedDevices();
if (!deviceList.isEmpty()) {
return deviceList.get(0);
}
}
return null;
}
/**
* 拨打
*
* @param mContext 蓝牙设备
* @param tel 号码
*/
public static void dial(Context mContext, String tel) {
if (TextUtils.isEmpty(tel)) {
Toast.makeText(mContext, "号码不可为空", Toast.LENGTH_SHORT).show();
return;
}
if (mBluetoothHeadsetClient == null) {
Log.e(TAG, "bluetoothHeadsetClient is null");
return;
}
List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
if (devices.isEmpty()) {
Toast.makeText(mContext, "未连接蓝牙设备", Toast.LENGTH_SHORT).show();
return;
}
BluetoothDevice device = devices.get(0);
mBluetoothHeadsetClient.dial(device, tel);
}
/**
* 接听
*
* @param mContext 蓝牙设备
* @param type BluetoothHeadsetClient.CALL_ACCEPT_NONE:正常接听(单路通话)
* BluetoothHeadsetClient.CALL_ACCEPT_HOLD:应答且挂起(多路通话)
* BluetoothHeadsetClient.CALL_ACCEPT_TERMINATE:接听且挂断(多路通话)
*/
public static void accept(Context mContext, int type) {
if (mBluetoothHeadsetClient == null) {
Log.e(TAG, "bluetoothHeadsetClient is null");
return;
}
List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
if (devices.isEmpty()) {
Toast.makeText(mContext, "未连接蓝牙设备", Toast.LENGTH_SHORT).show();
return;
}
BluetoothDevice device = devices.get(0);
mBluetoothHeadsetClient.acceptCall(device, type);
}
/**
* 挂断
*/
public static void terminate() {
if (mBluetoothHeadsetClient == null) {
Log.e(TAG, "bluetoothHeadsetClient is null");
return;
}
List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
if (devices.isEmpty()) {
Log.e(TAG, "Connected BluetoothDevice is null");
return;
}
if (mBluetoothHeadsetClientCall == null) {
Log.e(TAG, "mBluetoothHeadsetClientCall is null");
return;
}
mBluetoothHeadsetClient.terminateCall(devices.get(0), mBluetoothHeadsetClientCall);
}
/**
* 拒接
*/
public static void rejectCall() {
if (mBluetoothHeadsetClient == null) {
Log.e(TAG, "bluetoothHeadsetClient is null");
return;
}
List<BluetoothDevice> devices = mBluetoothHeadsetClient.getConnectedDevices();
if (devices.isEmpty()) {
Log.e(TAG, "Connected BluetoothDevice is null");
return;
}
mBluetoothHeadsetClient.rejectCall(devices.get(0));
}
/**
* HFP切换音频
*
* @param audioRoute: 0 音频切换手机
* 2 音频切换车机
*/
public static void setAudioMode(int audioRoute) {
if (mBluetoothHeadsetClient == null) {
Log.e(TAG, "bluetoothHeadsetClient is null");
return;
}
for (BluetoothDevice device : mBluetoothHeadsetClient.getConnectedDevices()) {
List<BluetoothHeadsetClientCall> currentCalls = mBluetoothHeadsetClient.getCurrentCalls(device);
if (currentCalls != null && !currentCalls.isEmpty()) {
if (audioRoute == BluetoothHeadsetClient.STATE_AUDIO_CONNECTED) { //2
mBluetoothHeadsetClient.connectAudio(device);
} else if ((audioRoute == BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED)) { //0
mBluetoothHeadsetClient.disconnectAudio(device);
}
}
}
}
/**
* 三方通话 - 保持通话
*
* @param mDevice 当前连接设备
*/
public static void onHold(BluetoothDevice mDevice) {
if (mBluetoothHeadsetClient != null) {
mBluetoothHeadsetClient.holdCall(mDevice);
}
}
public static void unHoldCall(Call call) {
if (call == null || call.getState() != Call.STATE_HOLDING) {
return;
}
if (mBluetoothHeadsetClient == null) {
return;
}
List<BluetoothDevice> connectedDevices = mBluetoothHeadsetClient.getConnectedDevices();
if (connectedDevices != null && !connectedDevices.isEmpty()) {
BluetoothDevice mDevice = connectedDevices.get(0);
mBluetoothHeadsetClient.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
}
}