AOSP实现蓝牙HFPClient功能

一、简介

  • 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)));
    }