Android蓝牙开发【六】hfp连接
本文主要内容是蓝牙手机音频的连接、断开流程分析,对应蓝牙HFP profile。
该文章是基于Android源码4.3的
1 hfp简单介绍 |
HFP (Hands-free Profile),让蓝牙设备(如蓝牙耳机)可以控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要看蓝牙耳机及电话是否支持。
HFP定义了音频网关(AG)和免提组件(HF)两个角色:
音频网关(AG) – 该设备为音频(特别是手机)的输入/输出网关。
免提组件(HF) – 该设备作为音频网关的远程音频输入/输出机制,并可提供若干遥控功能。
2 手机音频连接 |
对于手机音频的使用,首先连接的蓝牙设备需要支持hfp协议,并且需要与该设备进行配对,如何进行蓝牙配对这里就不细说了,可以参照我的其他文章。主要分析下其连接过程。
对于系统自带应用Settings中已配对的蓝牙设备界面(如下图所示),
其对应文件路径:
packages/apps/Settings/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
点击手机音频进行连接,调用onPreferenceChange。
public boolean onPreferenceChange(Preference preference, Object newValue) {if (preference == mDeviceNamePref) { //重命名mCachedDevice.setName((String) newValue);} else if (preference instanceof CheckBoxPreference) {//check boxLocalBluetoothProfile prof = getProfileOf(preference); //获取对应的profileonProfileClicked(prof, (CheckBoxPreference) preference);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-connectprofile.setPreferred(device, false); //设置对应profile的PRIORITY 为off,防止自动连接refreshProfilePreference(profilePref, profile); //刷新check box状态} else {profile.setPreferred(device, true); //设置对应profile的PRIORITY 为onmCachedDevice.connectProfile(profile); //连接指定profile}}
}
接着查看CachedBluetoothDevice中的connectProfile函数连接某一profile。
void connectProfile(LocalBluetoothProfile profile) {mConnectAttempted = SystemClock.elapsedRealtime();// Reset the only-show-one-error-dialog tracking variablemIsConnectingErrorPossible = true;connectInt(profile); //连接profilerefresh(); // 刷新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_ENVaddr = 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 forconnection state change, as it was causing a race condition, with the UI not beingupdated 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连接过程已经分析完了,而断开连接到过程和连接整体相差不多,就不再细说了。
Android蓝牙开发【六】hfp连接相关推荐
- android蓝牙配对 自动联接,如何实现android蓝牙开发 自动配对连接,并不弹出提示框...
之前做一个android版的蓝牙 与血压计通讯的项目,遇到最大的难题就是自动配对. 上网查资料说是用反射createBond()和setPin(),但测试时进行配对还是会出现提示,但配对是成功了 我就 ...
- android 提示蓝牙无法配对,如何实现android蓝牙开发 自动配对连接,并不弹出提示框...
之前做一个android版的蓝牙 与血压计通讯的项目,遇到最大的难题就是自动配对. 上网查资料说是用反射createBond()和setPin(),但测试时进行配对还是会出现提示,但配对是成功了 我就 ...
- Android蓝牙开发BLE-读写数据
上一篇:Android蓝牙开发BLE-蓝牙连接 上一篇写了BluetoothGattCallback的一个方法,判断了蓝牙是否连上.他还有好几个方法,读写就是在这里面进行,读写操作都在Bluetoot ...
- Android蓝牙开发教程(二)——连接蓝牙设备
在上一篇中已经介绍如何搜索附近可连接的蓝牙设备,如果你还没阅读过,建议先看看上一篇文章Android蓝牙开发教程(一)--搜索蓝牙设备 获取到设备后就可以开始处理蓝牙设备之间的连接. 在上一篇教程中我 ...
- Android蓝牙开发系列文章-蓝牙音箱连接
经过一段时间的折腾,我的Android Studio终于可以正常工作了,期间遇到的坑记录在了文章<创建Android Studio 3.5第一个工程遇到的坑>. 我们在<Androi ...
- Android蓝牙开发 — 经典蓝牙BLE蓝牙
一,前期基础知识储备 1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI): 支持移动电话.笔记本电脑.无线耳机等设备之间进行信息的交换: Android支持的蓝牙协议栈:Bl ...
- Android 蓝牙开发,申请打开蓝牙
申请打开蓝牙 <!-- 蓝牙权限 --> <uses-permission android:name="android.permission.BLUETOOTH" ...
- android蓝牙聊天设备,Android蓝牙开发——实现蓝牙聊天
最近课上刚好需要做一个课程设计关于蓝牙的就挑选了个蓝牙聊天室,其实关键还是在于对蓝牙API的了解 一.蓝牙API 与蓝牙开发主要的相关类是以下四个 BluetoothAdapter 字面上则理解为蓝牙 ...
- Android蓝牙开发系列文章-蓝牙设备类型知多少?
在写<Android蓝牙开发系列文章-蓝牙音箱连接>时,计划细化出一篇讲解蓝牙设备类型的文章,现在它来了~ 阅读其他内容,可以点击<Android蓝牙开发系列文章-策划篇>,或 ...
- Android蓝牙开发(一)之打开蓝牙和设备搜索
Android蓝牙开发系列目录: https://blog.csdn.net/huangliniqng/article/details/82185635 一.判断是否系统是否支持蓝牙 在使用蓝牙之前, ...
最新文章
- PM配置详解之三:维护计划、工作中心、作业列及产品资源与工具
- UCScript——C++集成脚本
- 在Microsoft System Center中利用您的现有投资管理VMware--Veeam MP v6.5
- 集福宝 支付宝2021年最新一款集福神器
- day30 java的IO流(3)
- 基于Docker部署LNMP架构
- java连接mysql数据库 R,java连接MySql数据库!
- FMS集群的安装和配置
- 在SharePoint Server 2007中创建定制的用户管理模块
- 【板栗糖GIS】如何将3dmax数据导入到超图软件中
- win10计算机桌面天气,win10电脑桌面显示时间和天气日期怎么设置
- Chrome浏览器离线安装Axure插件
- 激活函数(激励函数)理解总结
- Excel如何将列数据转换成行数据?分享技巧!excel怎么把列的信息变换为行的信息?
- Markdown编辑器如何配置图片大小
- 七成知识分子走在过劳死边缘
- i9级E52450处理器_来了!十代英特尔酷睿标压处理器,实测跑分首发
- 使用kettle来根据时间戳或者批次号来批量导入数据,达到增量的效果。
- centos安装rvm(菜鸟安装)
- 利用计算思维解决问题人和计算机都能完成,对计算思维能力养成与大学计算机基础课程改革的思考...