HFP (Hands-free Profile),让蓝牙设备(如蓝牙耳机)能够控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要看蓝牙耳机及电话是否支持。java
HFP定义了音频网关(AG)和免提组件(HF)两个角色:
音频网关(AG) – 该设备为音频(特别是手机)的输入/输出网关。
免提组件(HF) – 该设备做为音频网关的远程音频输入/输出机制,并可提供若干遥控功能。android
2 手机音频链接 |
对于手机音频的使用,首先链接的蓝牙设备须要支持hfp协议,而且须要与该设备进行配对,如何进行蓝牙配对这里就不细说了,能够参照个人其余文章。主要分析下其链接过程。
对于系统自带应用Settings中已配对的蓝牙设备界面(以下图所示),
其对应文件路径:
packages/apps/Settings/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
点击手机音频进行链接,调用onPreferenceChange。app
public boolean onPreferenceChange(Preference preference, Object newValue) {
函数
if (preference == mDeviceNamePref) { //重命名
ui
mCachedDevice.setName((String) newValue);
this
} else if (preference instanceof CheckBoxPreference) {//check box
spa
LocalBluetoothProfile prof = getProfileOf(preference); //获取对应的profile
代理
onProfileClicked(prof, (CheckBoxPreference) preference);
code
return false; // checkbox will update from onDeviceAttributesChanged() callback
对象
} else {
return false;
}
return true;
}
接着看onProfileClicked()函数处理
private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
BluetoothDevice device = mCachedDevice.getDevice(); //获取配对的蓝牙设备
int status = profile.getConnectionStatus(device); //获取profile的链接状态
boolean isConnected =
status == BluetoothProfile.STATE_CONNECTED;
if (isConnected) { //若是是链接状态则断开链接
askDisconnect(getActivity(), profile);
} else { //没有链接
if (profile.isPreferred(device)) { //获取profile是不是首选
// profile is preferred but not connected: disable auto-connect
profile.setPreferred(device, false); //设置对应profile的PRIORITY 为off,防止自动链接
refreshProfilePreference(profilePref, profile); //刷新check box状态
} else {
profile.setPreferred(device, true); //设置对应profile的PRIORITY 为on
mCachedDevice.connectProfile(profile); //链接指定profile
}
}
}
接着查看CachedBluetoothDevice中的connectProfile函数链接某一profile。
void connectProfile(LocalBluetoothProfile profile) {
mConnectAttempted = SystemClock.elapsedRealtime();
// Reset the only-show-one-error-dialog tracking variable
mIsConnectingErrorPossible = true;
connectInt(profile); //链接profile
refresh(); // 刷新ui
}
synchronized void connectInt(LocalBluetoothProfile profile) {
//查看是否配对,若是没有配对则进行配对,配对后进行链接,
//若是配对则直接链接
if (!ensurePaired()) {
return;
}
if (profile.connect(mDevice)) {//链接
return;
}
}
connectProfile() ——>connectInt()
connectInt()函数中会先判断是否配对,若是没有配对则开始配对,配对成功后链接profile。
若是已经配对则直接链接profile。
对于profile.connect(mDevice)会根据profile调用各自对应的connect方法。(如手机音频则对应HeadsetProfile,媒体音频对应A2dpProfile)。这里查看手机音频的链接HeadsetProfile。
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
//获取链接hfp的设备
List<BluetoothDevice> sinks = mService.getConnectedDevices();
if (sinks != null) {
for (BluetoothDevice sink : sinks) {
mService.disconnect(sink); //断开链接
}
} //链接hfp。
return mService.connect(device);
}
HeadsetProfile.java中的connect()方法,mService是经过getProfileProxy获取的BluetoothHeadset代理对象,经过其进行hfp相关操做。
mService.connect跳到Bluetooth应用中,
代码路径:packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java
先调用到内部类BluetoothHeadsetBinder的connect方法。
public boolean connect(BluetoothDevice device) {
HeadsetService service = getService();
if (service == null) return false;
return service.connect(device);
}
该方法中很明显是去调用HeadsetService的connect方法。
public boolean connect(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH ADMIN permission");
if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
return false; //检查priority
}
int connectionState = mStateMachine.getConnectionState(device);
if (connectionState == BluetoothProfile.STATE_CONNECTED ||
connectionState == BluetoothProfile.STATE_CONNECTING) {
return false; //检查链接状态
}
mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
return true;
}
HeadsetService的connect()函数会对priority和链接状态进行必要的检查,不符合条件则返回false。符合条件则向状态机发送消息HeadsetStateMachine.CONNECT。
此时HeadsetStateMachine中状态应该是Disconnected,因此查看Disconnected state中的处理
BluetoothDevice device = (BluetoothDevice) message.obj;
//发送广播,正在链接hfp
broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
//链接远端设备。
if (!connectHfpNative(getByteAddress(device)) ) {
//链接失败,向外发送链接失败广播
broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_CONNECTING);
break;
}
synchronized (HeadsetStateMachine.this) {
mTargetDevice = device;
transitionTo(mPending); //切换到pending状态
}
sendMessageDelayed(CONNECT_TIMEOUT, 30000);
HeadsetStateMachine调用connectHfpNative()函数来进行手机音频的链接。connectHfpNative是native方法,跳转到com_android_bluetooth_hfp.cpp中,调用对应的方法connectHfpNative
static jboolean connectHfpNative(JNIEnv *env, jobject object, jbyteArray address) {
jbyte *addr;
bt_status_t status;
if (!sBluetoothHfpInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
if ((status = sBluetoothHfpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
ALOGE("Failed HF connection, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
其中sBluetoothHfpInterface->connect会跳到蓝牙协议栈进行链接,协议栈就先不进行分析了。
3 链接状态 |
当协议栈链接状态改变会回调com_android_bluetooth_hfp.cpp中的方法connection_state_callback()。
static void connection_state_callback(bthf_connection_state_t state, bt_bdaddr_t* bd_addr) {
jbyteArray addr;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
(jint) state, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
在connection_state_callback方法中会从cpp层调用到java层,对应于HeadsetStateMachine中的onConnectionStateChanged函数
private void onConnectionStateChanged(int state, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = state;
event.device = getDevice(address);
sendMessage(STACK_EVENT, event);
}
onConnectionStateChanged函数中发送消息STACK_EVENT(携带状态和蓝牙地址),此时是Pending state,收到该消息调用processConnectionEvent。
正常链接成功应该会先收到HeadsetHalConstants.CONNECTION_STATE_CONNECTING状态,而后收到HeadsetHalConstants.CONNECTION_STATE_CONNECTED状态。
//发送广播,链接成功
broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
synchronized (HeadsetStateMachine.this) {
mCurrentDevice = mTargetDevice; //mCurrentDevice表示已链接的设备
mTargetDevice = null; //mTargetDevice表示要链接的设备
transitionTo(mConnected); //切换到Connected状态
}
收到HeadsetHalConstants.CONNECTION_STATE_CONNECTED状态,后向外发送链接成功的广播,状态机切换到Connected状态
private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
/* Notifying the connection state change of the profile before sending the intent for
connection state change, as it was causing a race condition, with the UI not being
updated with the correct connection state. */
mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET,newState, prevState);
Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
}
在mService.notifyProfileConnectionStateChanged中会将手机音频的proirty设置为auto_connect,而且向外发送BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED广播。
在其余应用中能够经过广播接收者注册BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED该广播,用来监听hfp的链接状态。
4 更新ui |
当手机音频链接成功后,Settings应用中会更新ui界面。
LocalBluetoothProfileManager中会对全部的profile进行管理,其将hfp的profile添加到BluetoothEventManager中,BluetoothEventManager会注册蓝牙状态改变、各profile状态改变等广播。
当BluetoothEventManager收到BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED广播后,会根据action获取对应的handler,调用对应handler的onReceive方法。
接收到该广播跳到LocalBluetoothProfileManager内部类StateChangedHandler.onReceive->CachedBluetoothDevice.onProfileStateChanged ->refresh ->dispatchAttributesChanged
接着跳到DeviceProfilesSettings中的onDeviceAttributesChanged ->refresh.这里会对界面进行更新,显示其链接状态信息。
hfp链接过程已经分析完了,而断开链接到过程和链接总体相差很少,就再也不细说了。