一、简介
- HFP(Handset-Free Profile),蓝牙免提通话的配置文件,用于支持蓝牙耳机的通话功能,在蓝牙设备的设置里显示为“通话”或“用于通话的音频”。
- HFP中有两方,一方为Audio Gateway(AG),一般是手机等智能设备;另一方为Handset-Free(HF),一般是蓝牙耳机、蓝牙音箱等设备,在Android中称为HFPClient。
- Android默认支持作为AG的Profile使用,不支持作为HF的Profile,所以HFPClient的API默认都为隐藏。本文主要讲解如何开启并调用Android的HFPClient功能。
二、编译AOSP以支持HFPClient
- AOSP中蓝牙支持的Profile定义位于蓝牙APK的资源文件
config.xml
中,路径为/packages/apps/Bluetooth/res/values/config.xml
,关于HFP默认配置如下:<bool name="profile_supported_hs_hfp">true</bool> <bool name="profile_supported_hfpclient">false</bool>
- 这表示Android设备仅作为AG使用,不能作为HF(也就是HFPClient),我们需要修改这里的值为:
<bool name="profile_supported_hs_hfp">false</bool> <bool name="profile_supported_hfpclient">true</bool>
- 修改之后,需要重新编译AOSP,编译AOSP的过程此处不做叙述。
三、调用HFPClient接口
- Android Frameworks中关于HFPClient的API位于
/frameworks/base/core/java/android/bluetooth/BluetoothHeadsetClient.java
中,但是这些API默认是私有的,不能直接调用,必须通过反射进行调用。 - 首先和通常的蓝牙开发相同,要首先获取默认的BluetoothAdapter:
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
- 然后获得Profile代理,但是这里要注意,Android SDK中是不包含HFPClient的Profile的,所以无法通过
BluetoothProfile.HEADSET_CLIENT
来获取,需要自己手动传入值:isHfpEnable =mBtAdapter.getProfileProxy(context, new ServiceListener(), 16);
- 这里
16
的来源如/frameworks/base/core/java/android/bluetooth/BluetoothProfile.java
中定义:/**
- Headset Client – HFP HF Role
- @hide
*/
int HEADSET_CLIENT = 16; ServiceListener
是一个内部类,实现了BluetoothProfile.ServiceListener
接口,在onServiceConnected
中首先检查了profile是否为BluetoothProfile.HEADSET_CLIENT
,也就是16,然后就可以获得BluetoothProfile
对象,以供后续使用:
class ServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.i(TAG, "onServiceConnected, profile="+profile);
if (profile == 16) {
mBtClient = proxy;
}
}
@Override
public void onServiceDisconnected(int profile) {
Log.i(TAG, "onServiceDisconnected, profile="+profile);
}
}
- 一切准备就绪后,就可以开始连接设备了,可以按照一般的搜索设备的方式进行连接,这里为了便捷,先在系统中完成配对,再通过以下接口进行检索,并选择一个:
mBtAdapter.getBondedDevices();
- 该方法返回一个
Set<BluetoothDevice>
对象,可从中选择一个作为我们的BluetoothDevice
- 然后我们就可以开始建立HFP连接,建立连接的方法位于
/frameworks/base/core/java/android/bluetooth/BluetoothHeadsetClient.java
中:/**
- Connects to remote device.
- Currently, the system supports only 1 connection. So, in case of the
- second connection, this implementation will disconnect already connected
- device automatically and will process the new one.
- @param device a remote device we want connect to
- @return <code>true</code> if command has been issued successfully; <code>false</code>
- otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")")
final IBluetoothHeadsetClient service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.connect(device);
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
return false;
}
}
if (service == null) Log.w(TAG, "Proxy not attached to service");
return false;
} - 需要通过反射调用该方法,实现如下:
public boolean connect() { if (mBtClient == null || btDevice == null) { return false; } try { Method method = Class.forName("android.bluetooth.BluetoothHeadsetClient") .getDeclaredMethod("connect", BluetoothDevice.class); return (Boolean) method.invoke(mBtClient, btDevice); } catch (ClassNotFoundException | NoSuchMethodException e) { Log.e(TAG, "connect, ClassNotFoundException | NoSuchMethodException"); e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException e) { Log.e(TAG, "connect, IllegalAccessException | InvocationTargetException"); e.printStackTrace(); } return false; }
- 这样就可以连接该设备,然后我们可以执行电话控制操作,比如拒接来电,拒接来电的实现同样位于
/frameworks/base/core/java/android/bluetooth/BluetoothHeadsetClient.java
中:/**
- Rejects a call.
- @param device remote device
- @return <code>true</code> if command has been issued successfully; <code>false</code>
- otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
- <p>Feature required for successful execution is being reported by: {@link
-
EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not
- supported.</p>
*/
public boolean rejectCall(BluetoothDevice device) {
if (DBG) log("rejectCall()");
final IBluetoothHeadsetClient service = mService;
if (service != null && isEnabled() && isValidDevice(device)) {
try {
return service.rejectCall(device);
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
}
}
if (service == null) Log.w(TAG, "Proxy not attached to service");
return false;
} - 依旧需要通过反射来调用:
public boolean rejectCall() { if (mBtClient == null || btDevice == null) { return false; } try { Method method = Class.forName("android.bluetooth.BluetoothHeadsetClient") .getDeclaredMethod("rejectCall", BluetoothDevice.class); return (Boolean) method.invoke(mBtClient, btDevice); } catch (ClassNotFoundException | NoSuchMethodException e) { Log.e(TAG, "connect, ClassNotFoundException | NoSuchMethodException"); e.printStackTrace(); } catch (IllegalAccessException | InvocationTargetException e) { Log.e(TAG, "connect, IllegalAccessException | InvocationTargetException"); e.printStackTrace(); } return false; }
- 这样就实现了在目标设备来电时拒接来电的功能。
四、HFPClient相关源码分析
- 在
/frameworks/base/core/java/android/bluetooth/BluetoothHeadsetClient.java
中,我们可以看到所有的操作都是通过IBluetoothHeadsetClient
这个AIDL实现的:private volatile IBluetoothHeadsetClient mService;
-
IBluetoothHeadsetClient
的定义位于/system/bt/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
中:/** * API for Bluetooth Headset Client service (HFP HF Role) * * {@hide} */ interface IBluetoothHeadsetClient { boolean connect(in BluetoothDevice device); boolean disconnect(in BluetoothDevice device); List<BluetoothDevice> getConnectedDevices(); List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); int getConnectionState(in BluetoothDevice device); boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); boolean startVoiceRecognition(in BluetoothDevice device); boolean stopVoiceRecognition(in BluetoothDevice device); List<BluetoothHeadsetClientCall> getCurrentCalls(in BluetoothDevice device); Bundle getCurrentAgEvents(in BluetoothDevice device); boolean acceptCall(in BluetoothDevice device, int flag); boolean holdCall(in BluetoothDevice device); boolean rejectCall(in BluetoothDevice device); boolean terminateCall(in BluetoothDevice device, in BluetoothHeadsetClientCall call); boolean enterPrivateMode(in BluetoothDevice device, int index); boolean explicitCallTransfer(in BluetoothDevice device); BluetoothHeadsetClientCall dial(in BluetoothDevice device, String number); boolean sendDTMF(in BluetoothDevice device, byte code); boolean getLastVoiceTagNumber(in BluetoothDevice device); int getAudioState(in BluetoothDevice device); boolean connectAudio(in BluetoothDevice device); boolean disconnectAudio(in BluetoothDevice device); void setAudioRouteAllowed(in BluetoothDevice device, boolean allowed); boolean getAudioRouteAllowed(in BluetoothDevice device); Bundle getCurrentAgFeatures(in BluetoothDevice device); }
- 而上述API的实现都位于
/packages/apps/Bluetooth/src/com/android/bluetooth/hfpclient
这个包中,JNI接口位于NativeInterface.java
中:// Native methods that call into the JNI interface static native void classInitNative(); static native void initializeNative(); static native void cleanupNative(); static native boolean connectNative(byte[] address); static native boolean disconnectNative(byte[] address); static native boolean connectAudioNative(byte[] address); static native boolean disconnectAudioNative(byte[] address); static native boolean startVoiceRecognitionNative(byte[] address); static native boolean stopVoiceRecognitionNative(byte[] address); static native boolean setVolumeNative(byte[] address, int volumeType, int volume); static native boolean dialNative(byte[] address, String number); static native boolean dialMemoryNative(byte[] address, int location); static native boolean handleCallActionNative(byte[] address, int action, int index); static native boolean queryCurrentCallsNative(byte[] address); static native boolean queryCurrentOperatorNameNative(byte[] address); static native boolean retrieveSubscriberInfoNative(byte[] address); static native boolean sendDtmfNative(byte[] address, byte code); static native boolean requestLastVoiceTagNumberNative(byte[] address); static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2, String arg);
- 这些JNI函数注册位于
/packages/apps/Bluetooth/jni/com_android_bluetooth_hfpclient.cpp
中static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initializeNative", "()V", (void*)initializeNative}, {"cleanupNative", "()V", (void*)cleanupNative}, {"connectNative", "([B)Z", (void*)connectNative}, {"disconnectNative", "([B)Z", (void*)disconnectNative}, {"connectAudioNative", "([B)Z", (void*)connectAudioNative}, {"disconnectAudioNative", "([B)Z", (void*)disconnectAudioNative}, {"startVoiceRecognitionNative", "([B)Z", (void*)startVoiceRecognitionNative}, {"stopVoiceRecognitionNative", "([B)Z", (void*)stopVoiceRecognitionNative}, {"setVolumeNative", "([BII)Z", (void*)setVolumeNative}, {"dialNative", "([BLjava/lang/String;)Z", (void*)dialNative}, {"dialMemoryNative", "([BI)Z", (void*)dialMemoryNative}, {"handleCallActionNative", "([BII)Z", (void*)handleCallActionNative}, {"queryCurrentCallsNative", "([B)Z", (void*)queryCurrentCallsNative}, {"queryCurrentOperatorNameNative", "([B)Z", (void*)queryCurrentOperatorNameNative}, {"retrieveSubscriberInfoNative", "([B)Z", (void*)retrieveSubscriberInfoNative}, {"sendDtmfNative", "([BB)Z", (void*)sendDtmfNative}, {"requestLastVoiceTagNumberNative", "([B)Z", (void*)requestLastVoiceTagNumberNative}, {"sendATCmdNative", "([BIIILjava/lang/String;)Z", (void*)sendATCmdNative}, };
int register_com_android_bluetooth_hfpclient(JNIEnv* env) {
return jniRegisterNativeMethods(
env, "com/android/bluetooth/hfpclient/NativeInterface",
sMethods, NELEM(sMethods));
}
- 以sendATCmdNative
函数为例,其也在该文件中定义:
```cpp
static jboolean sendATCmdNative(JNIEnv* env, jobject object, jbyteArray address,
jint cmd, jint val1, jint val2,
jstring arg_str) {
if (!sBluetoothHfpClientInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
const char* arg = NULL;
if (arg_str != NULL) {
arg = env->GetStringUTFChars(arg_str, NULL);
}
bt_status_t status = sBluetoothHfpClientInterface->send_at_cmd(
(const RawAddress*)addr, cmd, val1, val2, arg);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed to send cmd, status: %d", status);
}
if (arg != NULL) {
env->ReleaseStringUTFChars(arg_str, arg);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
- 其关键的代码为:
bt_status_t status = sBluetoothHfpClientInterface->send_at_cmd( (const RawAddress*)addr, cmd, val1, val2, arg);
sBluetoothHfpClientInterface
是一个结构体指针,定义为:static bthf_client_interface_t* sBluetoothHfpClientInterface = NULL;
-
bthf_client_interface_t
是一个结构体,包含一系列函数指针,于/system/bt/include/hardware/bt_hf_client.h
中定义:/** Represents the standard BT-HF interface. */ typedef struct { /** set to sizeof(BtHfClientInterface) */ size_t size; /** * Register the BtHf callbacks */ bt_status_t (*init)(bthf_client_callbacks_t* callbacks); /** connect to audio gateway */ bt_status_t (*connect)(RawAddress* bd_addr); /** disconnect from audio gateway */ bt_status_t (*disconnect)(const RawAddress* bd_addr); /** create an audio connection */ bt_status_t (*connect_audio)(const RawAddress* bd_addr); /** close the audio connection */ bt_status_t (*disconnect_audio)(const RawAddress* bd_addr); /** start voice recognition */ bt_status_t (*start_voice_recognition)(const RawAddress* bd_addr); /** stop voice recognition */ bt_status_t (*stop_voice_recognition)(const RawAddress* bd_addr); /** volume control */ bt_status_t (*volume_control)(const RawAddress* bd_addr, bthf_client_volume_type_t type, int volume); /** place a call with number a number * if number is NULL last called number is called (aka re-dial)*/ bt_status_t (*dial)(const RawAddress* bd_addr, const char* number); /** place a call with number specified by location (speed dial) */ bt_status_t (*dial_memory)(const RawAddress* bd_addr, int location); /** perform specified call related action * idx is limited only for enhanced call control related action */ bt_status_t (*handle_call_action)(const RawAddress* bd_addr, bthf_client_call_action_t action, int idx); /** query list of current calls */ bt_status_t (*query_current_calls)(const RawAddress* bd_addr); /** query name of current selected operator */ bt_status_t (*query_current_operator_name)(const RawAddress* bd_addr); /** Retrieve subscriber information */ bt_status_t (*retrieve_subscriber_info)(const RawAddress* bd_addr); /** Send DTMF code*/ bt_status_t (*send_dtmf)(const RawAddress* bd_addr, char code); /** Request a phone number from AG corresponding to last voice tag recorded */ bt_status_t (*request_last_voice_tag_number)(const RawAddress* bd_addr); /** Closes the interface. */ void (*cleanup)(void); /** Send AT Command. */ bt_status_t (*send_at_cmd)(const RawAddress* bd_addr, int cmd, int val1, int val2, const char* arg); } bthf_client_interface_t;
- 可以找到
send_at_cmd
的函数指针定义:/** Send AT Command. */ bt_status_t (*send_at_cmd)(const RawAddress* bd_addr, int cmd, int val1, int val2, const char* arg);
-
send_at_cmd
函数的定义位于/system/bt/btif/src/btif_hf_client.cc
中:/******************************************************************************* * * Function send_at_cmd * * Description Send requested AT command to rempte device. * * Returns bt_status_t * ******************************************************************************/ static bt_status_t send_at_cmd(const RawAddress* bd_addr, int cmd, int val1, int val2, const char* arg) { btif_hf_client_cb_t* cb = btif_hf_client_get_cb_by_bda(*bd_addr); if (cb == NULL || !is_connected(cb)) return BT_STATUS_FAIL; CHECK_BTHF_CLIENT_SLC_CONNECTED(cb); BTIF_TRACE_EVENT("%s: Cmd %d val1 %d val2 %d arg %s", __func__, cmd, val1, val2, (arg != NULL) ? arg : "<null>"); BTA_HfClientSendAT(cb->handle, cmd, val1, val2, arg); return BT_STATUS_SUCCESS; }
-
BTA_HfClientSendAT
的定义位于/system/bt/bta/hf_client/bta_hf_client_api.cc
中:/******************************************************************************* * * Function BTA_HfClientSendAT * * Description send AT command * * * Returns void * ******************************************************************************/ void BTA_HfClientSendAT(uint16_t handle, tBTA_HF_CLIENT_AT_CMD_TYPE at, uint32_t val1, uint32_t val2, const char* str) { tBTA_HF_CLIENT_DATA_VAL* p_buf = (tBTA_HF_CLIENT_DATA_VAL*)osi_malloc(sizeof(tBTA_HF_CLIENT_DATA_VAL)); p_buf->hdr.event = BTA_HF_CLIENT_SEND_AT_CMD_EVT; p_buf->uint8_val = at; p_buf->uint32_val1 = val1; p_buf->uint32_val2 = val2; if (str) { strlcpy(p_buf->str, str, BTA_HF_CLIENT_NUMBER_LEN + 1); p_buf->str[BTA_HF_CLIENT_NUMBER_LEN] = '\0'; } else { p_buf->str[0] = '\0'; } p_buf->hdr.layer_specific = handle; bta_sys_sendmsg(p_buf); }
-
bta_sys_sendmsg
的实现位于/system/bt/bta/sys/bta_sys_main.cc
中:/******************************************************************************* * * Function bta_sys_sendmsg * * Description Send a GKI message to BTA. This function is designed to * optimize sending of messages to BTA. It is called by BTA * API functions and call-in functions. * * TODO (apanicke): Add location object as parameter for easier * future debugging when doing alarm refactor * * * Returns void * ******************************************************************************/ void bta_sys_sendmsg(void* p_msg) { base::MessageLoop* bta_message_loop = get_message_loop(); if (!bta_message_loop || !bta_message_loop->task_runner().get()) { APPL_TRACE_ERROR("%s: MessageLooper not initialized", __func__); return; } bta_message_loop->task_runner()->PostTask( FROM_HERE, base::Bind(&bta_sys_event, static_cast<BT_HDR*>(p_msg))); }