最近看了一些大牛的博客,无不提到了技术分享的重要性,的确,分享自己的知识不仅仅是与他人讨论并相互提高的过程,更是对自己知识的梳理。希望自己能以此为契机,形成周期性博客分享的习惯。
文章标题为HFP的一些分析,HFP(Hand Free Profile)主要定义了安卓中与接打电话相关的一些功能的实现。本文主要介绍这其中对于HFP非常重要的Service Level Connection的建立。首先贴一个HFP实现的主要功能的图。
从此图也可以看到基本上都是与电话相关的功能。
这是SPEC中Service Level Connection建立的交互过程。其中HF为Hand Free,AG为Audio Gateway,这里可以认为是蓝牙耳机(HF)和手机(AG)。
从图中可以看到SLC的建立是以RFCOMM连接建立为基础,RFCOMM是串口模拟协议,这里不做讨论。然后开始进行AT命令的发送与回复。
接下来就到了喜闻乐见的代码阶段分析阶段了
为此我做了一张图
图中可以看到SLC建立的起点是从connect开始,就是当耳机和手机已经处于paired状态。这时候你点击该设备,安卓中应该都是此时开始连接。
这张图可能有的代码有一些跳跃,如果不明白的可以参照一下下面接听电话的代码流程图,因为两个流程有一些相同的地方。
android中的蓝牙连接操作是在settings里面完成的,所以最开始的onclick是在package/apps/Settings/Bluetooth中的BluetoothDevicePreference类中实现的onclicked方法中对connect进行调用,BluetoothDevicePreference这个类的具体含义这里不做描述,这里的onclick很明显指的是Bluetooth设备对应的那一行的按键click事件处理(其实我也不太明白这个类的含义,仍待继续研究)。接下来就像代码中的流程一样,图中有几处需要注意的是:
1.首先是在CachedBluetoothDevice这个类里的connectInt中的profile,这是java最基本的多态,使用父引用指向子对象。因为对HFP的实现是HeadsetProfile这个类,所以这里我们看该类中的connect函数。
2.在BluetoothHeadset中的connect通过安卓独特的IPC机制AIDL调用到Headsetservice中connect然后通过sendmessage进入HeadsetStatemachion状态机,对connectHfpNative进行了调用。
3.带有Native的JAVA层函数通过JNI调用跳到了CPP文件中,CPP文件是对JAVA层到C层的一个中转,可以实现JAVA层对下面native层的接口调用。
4.sBluetoothHfpInterface是通过btInf->get_profile_interface得到的,btInf由getBluetoothInterface得到,getBluetoothInterface返回了com_android_bluetooth_btservice_AdapterService.cpp中的 sBluetoothInterface变量,这个变量由HAL_MODULE_INFO_SYM->methods->open(module, id, &abstraction)中的abstraction->get_bluetooth_interface得到,具体操作可以追踪一下代码。
5。兜兜转转终于看到了connect的函数的真容,发现它是调用了btif_queue_connect(UUID_SERVCLASS_AG_HANDSFREE, bd_addr, connect_int)得到,前面的参数是一个宏后面的是connect_int接口,此处先不看connect_int,有点经验的应该都会知道后面会回调回来。
6.btif_queue_connect函数的核心是btif_transfer_context(queue_int_handle_evt, BTIF_QUEUE_CONNECT_EVT,(char *)&node, sizeof(connect_node_t), NULL);这里又传入了queue_int_handle_evt这一个函数接口以供后面回调,而上一点中的connect_int放在node的connect_cb中继续往下传。
7.在btif_queue_connect中的核心函数是btif_sendmsg(p_msg),其中p_msg携带了上一层中queue_int_handle_evt函数作为p_msg->p_cb,node作为p_msg->params,并调用了bluedroid中一个很重要的GKI_send_msg,这个函数是用来与Bluetooth几个重要的task进行通信的接口,通过这个函数的GKI_send_event与GKI_wait组成一组信号函数(具体的是pthread_cond系列的两个函数,有兴趣自行百度),可以进入task里面进行处理,Task由GKI_send_msg的参数决定,此处进入btif_task。
8.进入btif_task之后根据GKI_send_msg的参数找到btif_context_switched,终于发现了函数回调,此时回调了p_msg的p_cb,也就是前面提到的queue_int_handle_evt。
9.然后就是对profile队列的add和connect next操作,在queue_int_connect_next中最终是调用了p_head->connect_cb,但是在对HFP的处理中此处仍然调用了前面提到的connect_int,这个是在queue_int_add中的list_append的时候进行了处理,将connect_int赋给了head(此处我也没搞清楚里面的判断关系)。
10.在connect_int中有bta_AGopen然后像前面一样,调用到了GKI_send_msg,但是这次消息发送到了btu_task,在btu_task中对消息进行了处理,并调用了bta_sys_event函数,在这个函数中通过ID对bta_sys_cb中注册的函数进行调用,在bta_AGopen所进行的赋值BTA_AG_API_OPEN_EVT左移两位之后为BTA_ID_AG,因此此处的调用为bta_ag_hdl_event。
11.bta_ag_hdl_event中根据p_msg->event进行判断,最后调用到bta_ag_action中的bta_ag_start_open函数,在这个函数中调用了bta_ag_do_disc。然后我们回到那张图,发现AG在建立的时候其实就是等待HF发送第一个AT命令过来,所以SLC的建立分析至此就告以段落了。在往下层走需要日后慢慢继续研究。
(中间插播一下,没想到写一篇负责任一点的博客竟然如此之累,心中再一次对博客大牛们无限敬仰,能做到拿捏有度,抓住重点发表)
接下来是接听电话的流程分析
首相上一张SPEC里面的交互图
这本来是一张图,但是因为我截图的时候弄成了3张。
这张图比上一张图的代码流程步骤更加详细一点,是因为我是先作的这一张图,在前面的图里我就省略了一些过程,但是在BLOG里面因为上面已经介绍了上一个SLC的建立,所以这个过程的分析会依赖于上一个一些。
如图中
1.在接听电话的时候,起点明显是现有tele发起的,调用图中BluetoothPhoneService的UpdateHeadsetCallState方法,其中使用了前面一样的方法调用phoneStateChangeNative开始通过JNI往下层调用。
2.此处可以省略一大段前面介绍过的流程,图中也比较清晰,直接调用到bta_ag_result中的bta_ag_hfp_result.
3.bta_ag_hfp_result这个函数中的核心是bta_ag_send_call_inds这个函数,从名字也是可看出来是发送indicators的函数。具体应该发送什么ind,HFP spec上也有详细介绍。这个函数最终将会通过RFCOMM这个模拟串口将数据write到底层调用下面的协议提供的借口。
4.最后是一个关于audio connection的介绍,audio connection在这个情境中指的就是与SCO链路,而关于SCO链路的操作规则需要参照core SPEC。这里提一下的原因是接电话中两种情况inband和no-inband有所不同,就是对audio connection的连接需求,在SPEC上也有两张不同的流程图,无非是一个需要在接电话之前建立(如图),另一个却是在电话接通之后建立。在代码上面的体现或者说对于SCO的不同就是bta_ag_sco_open这个函数的调用时机不同,而SCO链路的建立并没有这么简单,这也是后面需要深入探究的地方。