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函数为例,其也在该文件中定义:
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)));
}