#过年不停更#OpenHarmony源码解析之电话子系统(call) 原创 精华
春节不停更,此文正在参加「星光计划-春节更帖活动」
作者:王大鹏
1. 简介
OpenHarmony电话子系统为OS提供了基础的无线通信能力,支持TD-LTE/FDD-LTE/TD-SCDMA/WCDMA/EVDO/CDMA1X/GSM等等网络制式的通信模块,能够提供高速的无线数据传输、互联网接入等业务,具备语音、短信、彩信,SIM卡等功能。
以下行文如无特别说明,所述说的鸿蒙系统均指OpenHarmony系统(OpenHarmony 3.0 LTS版本)。
1.1OpenHarmony架构图
2. 基础知识
2.1电话子系统
电话子系统做为OpenHarmony很重要的组成部分,为系统提供基础的通信功能,包括CS域的服务,比如语音呼叫、短信、呼叫管理,也包括PS域的相关服务,比如MMS、数据业务等,另外SIM和RIL的业务也在该子系统内。
2.2 电话子系统架构图
OpenHarmony现有电话子系统蜂窝通话通话相关框架图:
应用层 :各种需要通话、SMS、数据业务、SIM卡功能的应用,例如call应用,SMS应用,launcher应用等等。
框架层 :
1.SDK :给应用提供标准接口,包括JS接口和C++接口。
2.Framework:向应用层提供对应模块稳定的基础能力,包括network、call、sms,sim,相关的功能,包括通话管理,短彩编辑以及发送接收,sim卡的识别驻网,数据业务的管理等。在目前的OpenHarmony版本中,call_manager、cellular_call、cellular_data、data_storage、sms_mms、state_registry、core_service等模块都属于框架层。
Hril层 :相当于安卓的ril,由于不同方案使用的Modem不一样,所以各种指令格式,初始化序列都不一样,为了消除这些差别,而Hril则提供了无线硬件设备与电话服务之间的抽象层。
Vendor lib层 :类似于安卓的RILJ,负责与modem模块交互,发送各模块对应的AT命令。
Modem层:现在的基带处理器,主要处理数字信号、语音信号的编码解码以及通信协议,而基带处理器、射频和其它外围芯片作为一个 Modem 模块,提供 AT 命令接口给上层用于交互。通信模块Modem 通过与通信网络进行沟通,传输语音及数据,完成呼叫、短信等相关电话功能。
2.3电话子系统代码结构
由于电话子系统包含较多模块,所以将各模块分开描述:
通话管理模块:主要管理CS(Circuit Switch,电路交换)、IMS(IP Multimedia Subsystem,IP多媒体子系统)和OTT(over the top,OTT解决方案)三种类型的通话,负责申请通话所需要的音视频资源,并处理多路通话时产生的各种冲突。
蜂窝通话模块:支持基于运营商网络的基础通话实现,包含基于2G/3G的CS(Circuit Switch,电路交换)通话和基于4G/5G的IMS(IP Multimedia Subsystem,IP多媒体子系统)通话,包含VoLTE/ VoWIFI/ VoNR语音、视频、会议,支持CS和IMS通话之间的域选控制和切换,支持紧急通话。支持主流modem芯片平台。
蜂窝数据模块:作为电话子系统可裁剪部件,依赖于core_service核心服务、ril_adapter。 具有蜂窝数据激活、蜂窝数据异常检测与恢复、蜂窝数据状态管理、蜂窝数据开关管理、蜂窝数据漫游管理、APN管理、网络管理交互等功能。
电话核心服务模块:主要功能是初始化RIL管理、SIM卡和搜网模块,以及获取RIL Adapter服务。通过注册回调服务,实现与RIL Adapter进行通信;通过发布订阅,来实现与各功能模块的通信。
数据库及持久化模块:负责电话服务子系统中的SIM卡/短彩信等模块持久化数据存储,提供DataAbility访问接口。
RIL Adapter模块:主要包括厂商库加载,业务接口实现以及事件调度管理。主要用于屏蔽不同modem厂商硬件差异,为上层提供统一的接口,通过注册HDF服务与上层接口通讯。
短彩信模块:为移动数据用户提供短信收发和彩信编解码功能,主要功能有GSM/CDMA短信收发、短信PDU(Protocol data unit,协议数据单元)编解码、Wap Push接收处理 、小区广播接收、彩信通知、 彩信编解码和SIM卡短信记录增删改查等。
状态注册模块:主要负责提供电话服务子系统各种消息事件的订阅以及取消订阅的API。事件类型包括网络状态变化、信号强度变化、小区信息变化、蜂窝数据连接状态变化、通话状态变化等等。
2.4 相关仓
- 核心服务 :https://gitee.com/openharmony/telephony_core_service
- 蜂窝通话 :https://gitee.com/openharmony/telephony_cellular_call
- 通话管理 :https://gitee.com/openharmony/telephony_call_manager
- 注册服务 :https://gitee.com/openharmony/telephony_state_registry
- 短彩信 :https://gitee.com/openharmony/telephony_sms_mms
- riladapter:https://gitee.com/openharmony/telephony_ril_adapter
- 数据业务 :https://gitee.com/openharmony/telephony_cellular_data
- 数据存储 :https://gitee.com/openharmony/telephony_data_storage
- 网络管理 :https://gitee.com/openharmony/communication_netmanager_standard
2.5电话子系统(call)核心类
由于电话子系统所包含的模块太多,如果全部进行介绍会要很大篇幅,本次我们只对蜂窝通话核心业务模块进行进一步介绍:
所属服务 | 类名 | 功能 |
---|---|---|
call_manager | CallAbilityReportIpcProxy | 负责通话状态上报 |
CallManagerService | 负责通话服务管理 | |
CallControlManager | 负责通话控制管理 | |
CallStatusManager | 负责呼叫状态管理 | |
CallStatusCallback | 呼叫状态回调 | |
CSCall | 普通的CS呼叫相关处理 | |
IMSCall | IMS呼叫相关处理 | |
OTTCall | OTT呼叫相关处理(目前基本不支持) | |
AudioControlManager | 音频管理类 | |
BluetoothCallManager | 蓝牙呼叫管理 | |
VideoControlManager | 视频管理类 | |
cellular_call | CellularCallService | 通话管理层实现类 |
CellularCallRegister | 提供通话信息变化订阅功能 | |
CellularCallProxy | 提供蜂窝通话对外接口实现的代理 | |
CellularCallHandler | 处理RIL Adapter上报的消息 | |
CellularCallStub | 蜂窝通话业务层 | |
CSControl | 处理CS通话 | |
IMSControl | 处理IMS通话 | |
CellularCallConfig | 配置通话业务 | |
CellularCallSupplement | 通话补充业务 | |
BaseConnection | 会话连接 | |
ConfigRequest | 配置业务命令请求 | |
SupplementRequest | 补充业务命令请求 | |
core_service | Core | Core的初始化和各模块Manager交互的集合。 |
RilManager | Core核心服务与RIL Adapter通信管理代码 |
|
TelRilCall | Call相关核心服务与RIL Adapter通信代码 | |
ril_adapter | HRilManager | hril层管理类 |
HRilCall | hril层Call模块管理类 |
3. 源码解析
做为电话子系统的核心业务,通话功能(Call)除了需要硬件支持,比如音频模块,基带模块等,还需要系统本身的许多服务相互配合才能实现该功能,比如:通话管理(call_manager)、蜂窝通话服务(cellular_call)、Telephony核心服务(core_service)、RIL适配(ril_adapter),状态注册服务(state_registry)等。
通话目前主要分成三种:CS Call(Circuit Switch,电路交换)、IMS Call(IP Multimedia Subsystem,IP多媒体子系统)和OTT Call(over the top,OTT解决方案)三种类型的通话。对上层Call应用暴露的接口包括:dial、answer、reject、hangup、holdCall、unHoldCall、switchCal、startDTMF、stopDTMF等。由于事件较多,而且各事件处理流程比较类似,所以我们此处以Call的Answer事件处理流程来阐述。
3.1通话上层调用代码分析
当有电话呼入时,Callui界面会显示电话呼入,用户点击answer按键,则会激活Call的Answer流程:
此处会调用incomingCom.js的onAnswer函数:
由于之前已经import了callServiceProxy.js文件
所以我们来看下callServiceProxy的acceptCall 函数:
从代码中我们能看到此函数实际调用了call的answer函数,那么这个哪来的呢。实际这个call是由@ohos.telephony.call中引入的,从这里代码已经完成了从app到framework的调用。
3.2通话框架层代码分析
3.2.1通话框架层代码调用时序图
3.2.2通话框架层代码分析
从上边的时序图可以看出,整个Answer的调用流程是比较长的,整个框架层处理跨越包括call_manager、cellular_call、core_service、IPC、ril_adapter等多个服务。由于处理流程过长,具体的调用情况可以参照时序图,此处我们将框架层的处理一些关键地方根据调用不同的服务来进行描述。
3.2.2.1Answer事件在call_manager中的处理
之前应用层调用的call.answer是通过@ohos.telephony.call引入的,而实际定义在call_manage服务中的interfaces内的napi接口中实现:
注册要用到接口以及一些枚举参数
napi_value NapiCallManager::RegisterCallManagerFunc(napi_env env, napi_value exports)
这里我们需要的是CallBasis接口,具体内容如下所述,这些就是之前应用层调用的一些事件对应的处理函数。
因为我们对应的是answer事件,所以这里的对应函数是AnswerCall。
继续往下调用,从之前的函数看AnswerCall应该是异步处理的。
在一路调用的的过程中,会调用CallPolicy类的AnswerCallPolicy函数用来判断对应callId的CallObject是否存在,如有有就判断Call所处的状态。
在后续调用到CallRequestHandlerService的AnswerCall进行处理时,如果有CallRequestHandler的handler_存在,则make个AnswerCallPara的unique_ptr,然后将callid和videostate传入该指针。调用SendEvent将HANDLER_ANSWER_CALL_REQUEST发出。
从代码的处理流程看,这里发出的event会被同一个文件中的CallRequestHandler类捕获,触发ProcessEvent处理。
memberFuncMap会根据event的id从memberFuncMap_拿到对应的memberFunc,然后进入对应的处理函数:
这里对应的处理函数是AcceptCallEvent(),这部分函数无返回值,又从event中取出callId和videoState。
继续调用CallRequestProcessl类中的AnswerReques函数时,会通过GetOneCallObject拿到不同的CallObject。
拿到了对应的call之后,就可以调用对应的call的AnswerCall函数,这个函数会覆盖BaseCall的虚函数。由于CSCall,IMSCall,OTTCall都有对应的AnswerCall函数,所以实际使用那个是由BaseCall类的虚函数AnswerCall被那个子类重写,从而调用 不同的AnswerCall函数。这里我们假设为CsCall 。
调用到CarrierCall的CarrierAcceptCall进行后续处理。其中AcceptCallBase()函数会判断Call状态,如果是CALL_RUNNING_STATE_RINGING状态,则会调用AudioControlManager的SetVolumeAudible来设置audio的相关内容。
处理完AcceptCallBase()后,需要用PackCellularCallInfo将call的信息打包进给callinfo中,后续直接该callInfo传递出去。
下一步会调用CellularCallIpcInterfaceProxy类的来Answer函数来继续处理。首先要判断是否有ReConnectService的必要,这样操作是为了保证有可用的cellularCallInterfacePtr_。cellularCallInterfacePtr_是一个IRemoteBroker类,该类是一个IPC基类接口用于IPC通信。
在此之前call_manager已经在CellularCallIpcInterfaceProxy的初始化中将systemAbilityId(TELEPHONY_CELLULAR_CALL_SYS_ABILITY_ID)通过ConnectService()完成对应SA代理IRemoteObject的获取,然后构造对应的Proxy类,这样就能保证IPC通信的proxy和stub的对应。下节我们会看到,对应的stub其实是在cellular_call中注册的。
当调用到cellularCallInterfacePtr_->Answer时,CellularCallInterface的Answer虚函数,会被CellularCallProxy的Answer重写,所以实际执行CellularCallProxy的Answer函数,它是一个IRemoteProxy类。而callInfo信息则转换成MessageParcel 形式通过IPC的SendRequest发出。
从这里开始,Answer流程在call_manager的处理已经完成。
3.2.2.2 Answer事件在cellular_call中的处理
IPC(Inter-Process Communication)与RPC(Remote Procedure Call)机制用于实现跨进程通信,不同的是前者使用Binder驱动,用于设备内的跨进程通信,而后者使用软总线驱动,用于跨设备跨进程通信。IPC和RPC通常采用客户端-服务器(Client-Server)模型,服务请求方(Client)可获取提供服务提供方(Server)的代理 (Proxy),并通过此代理读写数据来实现进程间的数据通信。通常,Server会先注册系统能力(System Ability)到系统能力管理者(System Ability Manager,缩写SAMgr)中,SAMgr负责管理这些SA并向Client提供相关的接口。Client要和某个具体的SA通信,必须先从SAMgr中获取该SA的代理,然后使用代理和SA通信。下文使用Proxy表示服务请求方,Stub表示服务提供方。实现代码在/foundation/communication/ipc目录下。
SA注册与启动:SA需要将自己的AbilityStub实例通过AddSystemAbility接口注册到SystemAbilityManager。
SA获取与调用:通过SystemAbilityManager的GetSystemAbility方法获取到对应SA的代理IRemoteObject,然后构造AbilityProxy。这样就能保证proxy和stub的对应。
上一节我们在call_manager中看到了SA的获取过程,那这里我们来看下该SA的注册过程,还记得TELEPHONY_CELLULAR_CALL_SYS_ABILITY_ID这个systemAbilityId吗?它就是我们注册SA的关键。
这样我们就完成stub的注册,之前通过proxy发出的消息就会通过IPC传递到这里的stub中。IPC过程比较复杂,这里就不深入讨论了,有兴趣的同学可以自学一下。
由于IPCObjectStub的OnRemoteRequest是虚函数会被继承IPCObjectStub类的CellularCallStub 的OnRemoteRequest函数重写:
此处会根据map表来找到对应的处理函数,之前我们的code = 4是ANSWER,此处应该会进行对应的处理:
AnswerInner会进行后续的处理,这里会从data中解析出对应的callinfo信息,在函数末尾出调用本文件的Answer(),并将返回的结果写入reply中,这个返回值是函数执行的结果,为Int32整形值。
Answer函数是来完成跟ril交互的关键。首先根据callInfo的信息获得calltype,目前支持三种calltype,分别为cs_call、ims_call、ott_call。在确定calltype后,通过slotId_在GetCsControl()获得对应的CSControl对象。
这个调用到CSControl::Answer函数,会根据callInfo拿到对应的CellularCallConnectionCS 和CALL_STATUS,状态为来电、响铃、等待状态,则会调用CellularCallConnectionCS 对应的函数。
后续会调用CellularCallConnectionCS::AnswerRequest和core_service进行交互了。GetCore会根据slotId得到对应的Core,然后用Get函数得到对应的的event,设置Owner为CellularCallHandler,等待返回时进行回调对应的handler。
这里就完成了Answer流程在cellular_call的调用。
3.2.2.3 Answer事件在core_servicel中的处理
这部分的代码会调用core对应的Answer函数,消息就会传递到负责核心服务与RIL Adapter通信交互的tel_ril代码中。
进一步到负责tel_ril的管理类TelRilManager中,判断下对应的telRilCall_是否为空,如果不为空就可以继续调用了。
telRilCall_是在TelRilManager的初始化中进行的。其中InitCellularRadio首先通过ServiceManager拿到cellular_radio1对应的service。然后在InitTelInfo中用cellularRadio_和observerHandler_构造telRilCall_。
调用到teRilCall的Answer函数进行处理,用CreateTelRilRequest构造telRilRequest ,通过TelRilBase的SendInt32Event发出。
从调用的情况来看,则又通过IPC的SendRequest请求将dispatchId和data发给cellular_radio1。
到这里Answer流程在core_service服务中的处理就完毕了。
3.2.2.4 Answer事件在ril_adapter中的处理
从目前的代码调用来看有可能是从vendor调用hril_hdf的dispatch,然后传递消息到ril_adapter中
下边为hril_hdf的初始化过程:
可以看到hdf的初始化包括MODULE_ NAME和RilAdapterBind,RilAdapterInit。
Bind过程如下:
可以看看到service的dispatch对应于RilAdapterDispatch:
Init过程如下:
其中LoadVendor会加载对应vendor的rilLib
之前已经说到有可能是vendor调用了dispatch,现在已经绑定了对应的RilAdapterDispatch,后续处理如下:
C++写的IPC和C语言写的IPC是可以相互通信的,格式并不同。HdfSBuf也可以和MessageParcel互转。
Request消息通过IPC传到RilAdapter后会通过DispatchRequest分发出去:
在后续调用中我们知道code为HREQ_CALL_ANSWER,所以会调用DispatchModule:
根据slotId拿到对应g_manager的Dispatch函数:
由于我们的code = 4,在0到100之间,所以会被判定为callrequest
所以调用如下函数:
Request如下:
调用request对应处理函数为Answer:
首先要创建HRilRequest,将信息封装进requestInfo中。
在LoadVendor时调用HRilRegOps(ops)时会进行register
这里会将callOps注册到callFuncs_中,而g_callBacks实际是对应hrilOps:
.callOps 对应于 &g_callReqOps,对应的函数对照表如下
找对对应的ReqAnswer进行后续处理,这里其实已经走到AT命令的处理层:
后续调用SendCommandLock函数,这里就会将requestInfo处理成AT命令:
实际的处理在这个函数SendCommandNoLock:
通过WriteATCommand将AT命令发给modem,然后等待modem回复处理的结果:
到这里为止,Answer流程已经完成了从app到modem的信息传递。
总结:
从第三章的分析过程,我们已经完成了从Call ui响应来电提示,然后一步一步完成了整个框架层的调用过程,其实在结尾的时候我们只是完成了消息的下传到modem,这个过程后modem也进行回复,这个过程也比较长此处就不再赘述了。有机会的话我们可以在后续的文章中进行分析。
整个文档由于本人能力和时间的限制,后续尽可能将遗漏地方补全。从core_service到ril_adapter的调用,是在vendor目录中将cellular_radio1的libhril_hdf.z.so加载。由于目前的代码还在完善中,有可能后续最新版的代码会有所改变。
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
这个模块讲的太详细了!期待老师其他文章。另外文中图片方便放附件一份吗,文章中压得有点看不清了。
已添加附件
谢谢老师
讲得非常详细,非常清晰,点赞
老师,
“从core_service到ril_adapter的调用,是在vendor目录中将cellular_radio1的libhril_hdf.z.so加载” 这部分代码现在完善了么?调用流程有介绍么?非常感谢!