OpenHarmony最新电话子系统CS 实现梳理解读
电话子系统 CS 实现梳理
文档概述
说明:
1.文章由移远通信技术股份有限公司提供
2.以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改(181-0715-8288)
1. 文档目的
本文基于 base\telephony 目录下的实际源码,梳理 OpenHarmony 电话子系统中 CS 电话的实现方式。
文中的 CS 指 Circuit Switched,也就是传统 2G/3G 电路域语音电话。与之对应的还有:
IMS:4G/5G 的 IMS 语音/视频/会议通话OTT:互联网通话
本文主要覆盖以下内容:
- CS 电话在各模块中的职责边界
- CS 电话的请求下行链路
- CS 电话的事件和状态上行链路
- CS 与 IMS/OTT 的分层关系
1.1 适合读者
这篇文章更适合下面几类读者:
- 刚开始接触 OpenHarmony 电话子系统源码,希望先抓主链路的人
- 已经在看
base/telephony,但对call_manager、cellular_call、core_service、ril_adapter的边界还不够清楚的人 - 需要排查拨号、接听、挂断问题,想先建立一套定位路径的人
- 需要做内部分享、知识传递或二次整理的人
1.2 阅读收益
读完这篇文章,至少可以拿到下面几项信息:
- 知道 CS 电话请求是怎么从上层一路走到 modem 的
- 知道状态和事件是怎么从底层一路回到
call_manager的 - 知道 CS、IMS、OTT 三类通话在当前代码树里的边界
- 知道排查问题时应该先看哪几个模块、哪几个文件
1.3 阅读建议
如果是第一次看这个方向,建议按下面的顺序阅读:
- 先看“总体结论”,建立主链路印象
- 再看“模块边界”和“请求下行链路”,弄清楚分层
- 然后看“时序图”和“详细调用链”,把请求和状态串起来
- 最后看“排查路径”“误区与 FAQ”,把文章内容转成可落地的分析方法
2. 总体结论
在当前代码树中,CS 电话主链路如下:
应用/系统电话界面 -> call_manager -> cellular_call -> CSControl -> CellularCallConnectionCS -> core_service -> ril_adapter -> vendor AT
完整调用链如下:
call.dial -> CallManagerService -> CallRequestProcess -> CellularCallConnection -> CellularCallService -> CSControl -> CellularCallConnectionCS -> CoreManagerInner -> TelRilManager -> HRilManager -> HRilCall -> at_call.c
代码结构如下:
call_manager负责统一呼叫管理,面向上层能力出口cellular_call负责蜂窝通话域的实际控制,并在 CS/IMS/卫星之间做分流core_service负责把呼叫控制请求继续下发给电话底层ril_adapter负责统一屏蔽不同 modem 的差异vendor/at_call.c是当前仓内可见的最终拨号落点之一
3. 模块边界
3.1 call_manager
call_manager 是统一的呼叫管理模块。其 README 明确说明它同时管理三类呼叫:
- CS
- IMS
- OTT
它的职责不是直接和 modem 通信,而是:
- 管理呼叫对象和状态
- 处理拨号、接听、挂断等统一操作
- 管理音频、视频、蓝牙等通话资源
- 和
cellular_call、core_service协作
相关代码:
call_manager/README.mdcall_manager/services/call_manager_service/src/call_manager_service.cppcall_manager/services/call/src/call_request_process.cppcall_manager/services/telephony_interaction/src/cellular_call_connection.cpp
3.2 cellular_call
cellular_call 是蜂窝通话模块。它的 README 明确说明:
- 支持 2G/3G 的 CS 通话
- 支持 4G/5G 的 IMS 通话
- 支持 CS/IMS 域选择和切换
- 不对三方直接暴露 API,而是由
call_manager直接调用
关键职责分层如下:
manager:管理层,服务入口、回调处理、注册分发control:控制层,包含CSControl、IMSControlconnection:连接层,把控制请求下发给core_service
相关代码:
cellular_call/README.mdcellular_call/services/manager/src/cellular_call_service.cppcellular_call/services/control/src/cs_control.cppcellular_call/services/connection/src/cellular_call_connection_cs.cppcellular_call/services/manager/src/cellular_call_handler.cppcellular_call/services/manager/src/cellular_call_register.cpp
3.3 core_service
core_service 在 CS 实现中起到“把通话控制请求继续下发给 RIL”的作用。
关键路径:
core_service/frameworks/native/src/core_manager_inner.cppcore_service/services/tel_ril/src/tel_ril_manager.cpp
3.4 ril_adapter
ril_adapter 的职责是:
- 屏蔽不同 modem 厂商实现差异
- 对 telephony 服务层提供统一接口
- 通过 HDF 服务和底层厂商实现通信
关键路径:
ril_adapter/README.mdril_adapter/services/hril/src/hril_manager.cppril_adapter/services/hril/src/hril_call.cppril_adapter/services/vendor/src/at_call.c
4. 系统能力与进程分工
从 SA 配置看,核心服务分为两层:
4.1 call_manager
- SA 号:
4005 - 进程:
telecom - 动态库:
libtel_call_manager.z.so
来源:
call_manager/sa_profile/4005.json
4.2 cellular_call
- SA 号:
4006 - 进程:
telephony - 动态库:
libtel_cellular_call.z.so - 依赖:
4010,即core_service
来源:
cellular_call/sa_profile/4006.json
从架构分工看:
call_manager偏业务管理和统一入口cellular_call偏蜂窝呼叫执行core_service是cellular_call的下游依赖
5. CS 电话请求下行链路
5.1 上层请求进入 call_manager
call_manager 作为统一入口启动后,会在初始化时连接 cellular_call:
- 初始化
CallControlManager - 初始化
CellularCallConnection - 使用
TELEPHONY_CELLULAR_CALL_SYS_ABILITY_ID建立和cellular_call的连接
关键代码:
call_manager/services/call_manager_service/src/call_manager_service.cpp
其中:
CallManagerService::Init()会调用CellularCallConnection::Init(TELEPHONY_CELLULAR_CALL_SYS_ABILITY_ID)
5.2 CallRequestProcess 发起拨号
拨号真正开始时,CallRequestProcess::HandleStartDial() 会:
- 把上层拨号参数封装成
CellularCallInfo - 调用
DelayedSingleton<CellularCallConnection>::GetInstance()->Dial(callInfo)
关键代码:
call_manager/services/call/src/call_request_process.cpp
这一步意味着:
call_manager不直接操作 RIL- 所有蜂窝电话控制统一通过
CellularCallConnection转给cellular_call
5.3 CellularCallConnection 通过 IPC 调用 cellular_call
CellularCallConnection 的职责是 call_manager -> cellular_call 的 IPC 桥接。
主要行为:
- 通过系统能力管理器获取
cellular_call远程对象 - 转换为
CellularCallInterface - 注册
ICallStatusCallback - 调用远程接口
Dial/HangUp/Answer/Reject/...
关键代码:
call_manager/services/telephony_interaction/src/cellular_call_connection.cpp
关键函数:
ConnectService()RegisterCallBackFun()Dial(const CellularCallInfo &callInfo)
到这里,请求已经跨进程进入 cellular_call 服务。
5.4 CellularCallService 做 CS/IMS/卫星分流
CellularCallService::Dial() 和 DialNormalCall() 是整个蜂窝呼叫域的总分发入口。
分流逻辑如下:
- 先校验 slot
- 判断是否处于 SRVCC 特殊状态
- 判断是否是紧急号码
- 如果卫星电话打开,走
SatelliteControl - 否则判断是否需要 IMS
- 如果需要 IMS,走
IMSControl - 否则走
CSControl
关键代码:
cellular_call/services/manager/src/cellular_call_service.cpp
对普通 CS 电话来说,关键分流点是:
DialNormalCall()return csControl->Dial(callInfo, isEcc);
5.5 CSControl 执行 CS 侧拨号控制
CSControl 是 CS 电话控制核心。
CSControl::Dial() 的主要处理步骤如下:
- 记录 HiSysEvent 参数
- 执行拨号前置校验
DialPreJudgment - 查询 CS 注册状态
GetCsRegState - 如果不在服务中且不是紧急电话,则返回失败
- 查询当前网络类型
GetNetworkStatus - 按网络类型进入:
DialGsm()DialCdma()
关键代码:
cellular_call/services/control/src/cs_control.cpp
这一步清楚地说明:
CSControl不是“统一拨号类”- 它会根据当前网络类型区分 GSM 和 CDMA 逻辑
5.6 DialGsm() / DialCdma() 的处理特点
DialGsm()
主要逻辑:
- 去除号码分隔符
- 判断是否是 MMI 码
- 检查当前呼叫状态是否允许继续发起新呼叫
- 如果已有 active 通话,先通过
SwitchCallRequest()做切换/保持 - 最终调用
EncapsulateDialCommon()
DialCdma()
主要逻辑:
- 去除号码分隔符
- 判断是否是 MMI 码
- 检查当前呼叫状态
- 如果已存在 active 连接,走
SendCDMAThreeWayDialRequest() - 否则进入
EncapsulateDialCommon()
从实现看:
- GSM 更偏向 3GPP 的保持/切换/多方通话行为
- CDMA 有自己的三方通话控制分支
5.7 EncapsulateDialCommon() 封装拨号请求
EncapsulateDialCommon() 会把实际拨号参数组装成 DialRequestStruct:
phoneNumclirMode
然后:
- 调用
CellularCallService::SetMute(slotId, false) - 实例化
CellularCallConnectionCS - 调用
csConnection.DialRequest(slotId, dialRequest)
关键代码:
cellular_call/services/control/src/cs_control.cpp
从职责划分看:
CSControl负责业务判断- 真正的“下沉请求”由
CellularCallConnectionCS完成
5.8 CellularCallConnectionCS 向 core_service 下发请求
CellularCallConnectionCS::DialRequest() 会:
- 获取
CellularCallService实例 - 获取对应 slot 的
handler - 更新号码信息
- 调用
CoreManagerInner::GetInstance().Dial(...)
关键代码:
cellular_call/services/connection/src/cellular_call_connection_cs.cpp
同一类里的其他 CS 操作也按同样模式下发:
HangUpRequest()AnswerRequest()RejectRequest()HoldRequest()UnHoldCallRequest()SwitchCallRequest()CombineConferenceRequest()SeparateConferenceRequest()CallSupplementRequest()SendDtmfRequest()
CellularCallConnectionCS 本质上就是一层 CS 请求桥接。
5.9 CoreManagerInner 转到 TelRilManager
CoreManagerInner::Dial() 的逻辑很直接:
- 检查
telRilManager_ - 创建对应
InnerEvent - 把
handler绑定给response - 调用
telRilManager_->Dial(slotId, address, clirMode, response)
关键代码:
core_service/frameworks/native/src/core_manager_inner.cpp
这一层的职责是:
core_service负责“事件对象 + 下层管理器分发”- 同时继续维持异步回调模型
5.10 TelRilManager 转到 HRilManager
TelRilManager::Dial() 通过 TaskSchedule(...) 把请求转给 TelRilCall::Dial(...)。
关键代码:
core_service/services/tel_ril/src/tel_ril_manager.cpp
从这里开始,CS 请求就进入纯底层调度阶段,上层业务语义基本不再参与。
5.11 HRilManager / HRilCall 下沉到 vendor
HRilManager::Dial() 会继续把请求交给 HRilCall::Dial():
HRilManager::Dial(slotId, serialId, dialInfo)HRilCall::Dial(serialId, dialInfo)
HRilCall::Dial() 会:
- 构造
HRilDial - 把地址和 CLIR 填进去
- 调用
RequestVendor(...)
关键代码:
ril_adapter/services/hril/src/hril_manager.cppril_adapter/services/hril/src/hril_call.cpp
5.12 at_call.c 最终执行 AT 命令
在当前可见仓内,拨号最终落到:
ril_adapter/services/vendor/src/at_call.c
ReqDial() 的核心逻辑是:
- 根据
clir组装 CLIR 参数 - 生成 AT 命令
- 命令格式为:
ATD<number><clir>;
- 通过
SendCommandLock()发给 modem - 回传拨号结果
同文件中还能看到:
- 挂断:
AT+CHLD - 接听:通常对应
ATA - 拒接/结束:通常对应
ATH或相关补充命令
结合当前仓内代码,可以确认:
- CS 呼叫最终是通过传统 AT 命令方式落到 modem 的
6. CS 电话状态上行链路
CS 电话不仅有请求下发,还有状态和事件的上行回报。
6.1 cellular_call 向 call_manager 注册回调
在 CellularCallConnection::ConnectService() 中,call_manager 会向 cellular_call 注册回调:
RegisterCallBackFun()- 最终调用
cellularCallInterfacePtr_->RegisterCallManagerCallBack(cellularCallCallbackPtr_)
关键代码:
call_manager/services/telephony_interaction/src/cellular_call_connection.cpp
对应的服务端接收点在:
cellular_call/services/manager/src/cellular_call_stub.cppcellular_call/services/manager/src/cellular_call_service.cpp
最终存放到:
CellularCallRegister::RegisterCallManagerCallBack(...)
6.2 CellularCallHandler 处理底层事件
CellularCallHandler 是蜂窝呼叫事件处理中心,负责处理:
- 拨号结果
- 接听结果
- 挂断结果
- DTMF 结果
- PostDial 行为
- 交换通话结果
关键代码:
cellular_call/services/manager/src/cellular_call_handler.cpp
例如:
ExecutePostDial()会根据isIms选择转给IMSControl或CSControl- 对于 CS,最终执行
csControl->ExecutePostDial(slotId_, callId)
6.3 CellularCallRegister 把状态回报给 call_manager
CellularCallRegister 持有 callManagerCallBack_,并提供统一的状态上报接口:
ReportSingleCallInfo()ReportEventResultInfo()ReportCallsInfo()- 各类补充业务结果上报
关键代码:
cellular_call/services/manager/src/cellular_call_register.cpp
例如:
ReportSingleCallInfo()最终调用callManagerCallBack_->UpdateCallReportInfo(...)ReportEventResultInfo()最终调用callManagerCallBack_->UpdateEventResultInfo(...)
状态回流链路可以概括为:
modem/RIL -> handler -> CellularCallRegister -> ICallStatusCallback -> call_manager
7. CS 关键能力点
从当前源码看,CS 实现有几个比较明确的特征。
7.1 按网络制式区分 GSM 和 CDMA
在 CSControl::Dial() 中,明确按 PhoneType 分流:
PHONE_TYPE_IS_GSMPHONE_TYPE_IS_CDMA
这表明 CS 层不是单一路径,而是根据接入网络执行不同控制逻辑。
7.2 依赖 CS 域注册状态
CSControl::Dial() 会检查:
GetCsRegState(slotId)
如果设备不在 CS 域服务中,且当前不是紧急号码,则拨号失败。
7.3 支持 MMI 码
在 DialGsm() / DialCdma() 中,都会先调用:
IsNeedExecuteMMI(...)
这表明星号井号类号码并不总是普通拨号,它们可能转为 MMI 补充业务处理。
7.4 支持多路通话和补充业务
CS 侧支持的操作不仅有:
- 拨号
- 接听
- 拒接
- 挂断
还包括:
- 保持/恢复
- 交换通话
- 合并会议
- 拆分会议
- DTMF
- 补充业务
这些能力在 CellularCallConnectionCS 和 CSControl 中都有明确入口。
8. CS 与 IMS/OTT 的关系
8.1 在 call_manager 看,三者都是被统一管理的呼叫类型
call_manager/README.md 明确说明:
call_manager同时管理CS、IMS、OTT
从顶层呼叫管理角度看,它们都属于统一的电话/通话对象管理范围。
8.2 在 cellular_call 看,CS 和 IMS 是蜂窝电话内部的两条实现分支
cellular_call/README.md 和 CellularCallService::DialNormalCall() 说明:
IMSControl对应 IMS 呼叫CSControl对应 CS 呼叫
对应到实现上:
cellular_call负责决定这次蜂窝通话走 IMS 还是 CS- 一旦选中 CS,后续就全部沿
CSControl路径执行
8.3 OTT 不走 cellular_call
OTT 在 call_manager 中被统一管理,但从当前目录结构和调用链看:
- OTT 不会走
cellular_call -> CSControl -> RIL - 它属于另一类通话实现,不依赖蜂窝电路域
9. 培训视角下的总览图
这一部分先把整体关系铺开,便于后面再落到具体源码。
这一部分按下面顺序看会更顺:
- 先讲谁负责接请求
- 再讲谁决定走 CS 还是 IMS
- 再讲谁真正把请求送到 modem
- 最后讲状态是怎么逐层回来的
9.1 模块关系图
flowchart LR
A[应用 / 系统电话界面] --> B[call_manager]
B --> C[cellular_call]
C --> D[CSControl]
D --> E[CellularCallConnectionCS]
E --> F[core_service]
F --> G[ril_adapter]
G --> H[vendor AT / modem]
H --> G
G --> F
F --> C
C --> B
B --> A
这张图主要看两点:
- 左到右是请求下行链路
- 右到左是结果和状态回传链路
9.2 模块职责图
flowchart TB
subgraph L1[统一呼叫管理层]
A1[call_manager]
end
subgraph L2[蜂窝呼叫控制层]
B1[CellularCallService]
B2[CSControl]
B3[IMSControl]
end
subgraph L3[电话底层服务层]
C1[CoreManagerInner]
C2[TelRilManager]
C3[HRilManager / HRilCall]
end
subgraph L4[厂商适配层]
D1[vendor AT]
D2[modem]
end
A1 --> B1
B1 --> B2
B1 --> B3
B2 --> C1
C1 --> C2
C2 --> C3
C3 --> D1
D1 --> D2
这一页可以先记住一句话:
call_manager负责“统一管理”cellular_call负责“业务分流和控制”core_service + ril_adapter负责“下沉执行”
10. 时序图
这一章只看两件事:请求怎么下去,状态怎么回来。
10.1 CS 拨号时序图
sequenceDiagram
participant APP as 应用/电话界面
participant CM as call_manager
participant CC as CellularCallConnection
participant SVC as CellularCallService
participant CSC as CSControl
participant CONN as CellularCallConnectionCS
participant CORE as CoreManagerInner
participant TRM as TelRilManager
participant HRIL as HRilManager/HRilCall
participant AT as at_call.c
participant MODEM as Modem
APP->>CM: dial
CM->>CC: Dial(callInfo)
CC->>SVC: IPC Dial(callInfo)
SVC->>SVC: 判断卫星/IMS/CS
SVC->>CSC: CSControl::Dial()
CSC->>CSC: 校验网络、号码、MMI、通话状态
CSC->>CONN: DialRequest(slotId, dialRequest)
CONN->>CORE: CoreManagerInner::Dial(...)
CORE->>TRM: TelRilManager::Dial(...)
TRM->>HRIL: HRilCall::Dial(...)
HRIL->>AT: ReqDial()
AT->>MODEM: ATD<number>;
MODEM-->>AT: 返回结果
AT-->>HRIL: 上报响应
HRIL-->>TRM: 返回事件
TRM-->>CORE: 返回事件
CORE-->>SVC: handler 回调
SVC-->>CC: callback / register
CC-->>CM: UpdateCallReportInfo
CM-->>APP: 更新界面和通话状态
有两个转折点:
CellularCallService是“选路点”CellularCallConnectionCS是“从业务控制进入底层控制的桥”
10.2 CS 来电接听时序图
sequenceDiagram
participant MODEM as Modem
participant AT as at_call.c
participant HRIL as HRilManager/HRilCall
participant CORE as CoreManagerInner
participant SVC as CellularCallService/Handler
participant REG as CellularCallRegister
participant CM as call_manager
participant UI as 电话界面
MODEM-->>AT: 来电状态
AT-->>HRIL: 通话事件
HRIL-->>CORE: 呼叫状态上报
CORE-->>SVC: event handler
SVC-->>REG: ReportSingleCallInfo
REG-->>CM: ICallStatusCallback
CM-->>UI: 展示来电
UI->>CM: Answer
CM->>SVC: IPC Answer(callInfo)
SVC->>SVC: 路由到 CSControl
SVC->>CORE: CoreManagerInner::Answer(...)
CORE->>HRIL: Answer
HRIL->>AT: ReqAnswer()
AT->>MODEM: ATA
10.3 CS 挂断时序图
sequenceDiagram
participant UI as 电话界面
participant CM as call_manager
participant SVC as CellularCallService
participant CSC as CSControl
participant CONN as CellularCallConnectionCS
participant CORE as CoreManagerInner
participant HRIL as HRilManager/HRilCall
participant AT as at_call.c
participant MODEM as Modem
UI->>CM: HangUp
CM->>SVC: IPC HangUp(callInfo)
SVC->>CSC: CSControl::HangUp()
CSC->>CSC: 查找 connection / 判断挂断类型
CSC->>CONN: HangUpRequest 或 CallSupplementRequest
CONN->>CORE: CoreManagerInner::Hangup(...)
CORE->>HRIL: Hangup
HRIL->>AT: ReqHangup()
AT->>MODEM: AT+CHLD=1x
MODEM-->>AT: 返回结果
AT-->>HRIL: 上报结果
HRIL-->>CORE: 回调
CORE-->>SVC: handler 处理
SVC-->>CM: 上报断开状态
CM-->>UI: 更新已挂断
11. 详细调用链
11.1 拨号调用链
第 1 段:统一入口
- 应用或系统电话界面发起拨号
CallManagerService作为呼叫系统能力入口已提前启动CallRequestProcess::HandleStartDial()封装CellularCallInfoDelayedSingleton<CellularCallConnection>::GetInstance()->Dial(callInfo)发起跨进程调用
这一段对应统一入口层:
call_manager只做统一调度和参数封装- 真正的蜂窝呼叫执行不在
call_manager内部完成
第 2 段:蜂窝呼叫总分流
CellularCallConnection::Dial()调用远端cellular_callCellularCallService::Dial()做总体校验CellularCallService::DialNormalCall()决定走IMSControl还是CSControl- 对于普通 CS 呼叫,进入
CSControl::Dial(callInfo, isEcc)
这一段对应蜂窝呼叫总分流:
- 这是整个电话子系统最关键的分流点
- 后续是否走 CS,取决于这一层的判断结果
第 3 段:CS 业务控制
CSControl::Dial()先做拨号前校验- 检查
GetCsRegState(slotId),确认 CS 域可用 - 检查
GetNetworkStatus(slotId),区分GSM和CDMA - 分别进入:
DialGsm()DialCdma()
这一段对应 CS 业务控制:
- CS 拨号并不是“一个通用拨号函数直接到底”
- 它先依赖网络状态、注册状态和当前呼叫状态做业务判断
第 4 段:GSM/CDMA 分支处理
DialGsm() 主要做:
- 号码标准化
- MMI 判断
- 通话状态校验
- 如果已有 active 通话,则先
SwitchCallRequest() - 最终调用
EncapsulateDialCommon()
DialCdma() 主要做:
- 号码标准化
- MMI 判断
- 通话状态校验
- 如果已有 active 通话,则走
SendCDMAThreeWayDialRequest() - 否则继续
EncapsulateDialCommon()
这里的结论是:
- GSM 和 CDMA 的行为差异,不在最底层,而是在
CSControl就已经开始体现
第 5 段:下沉到底层
EncapsulateDialCommon()组装DialRequestStructCellularCallConnectionCS::DialRequest()调用CoreManagerInner::Dial(...)CoreManagerInner::Dial()调用TelRilManager::Dial(...)TelRilManager::Dial()调用TelRilCall::Dial(...)- 下游再进入
HRilManager::Dial()和HRilCall::Dial() HRilCall::Dial()通过RequestVendor(...)进入ReqDial()ReqDial()组装 AT 命令:ATD<number><clir>;
这一段把“业务控制”和“底层执行”分得最清楚。
11.2 接听调用链
第 1 段:来电状态先上报
- modem 侧产生来电状态
- 底层事件逐层回到
CellularCallHandler CellularCallRegister::ReportSingleCallInfo()把状态回传给call_managercall_manager再驱动界面显示来电
这一步的前提是:
- 接听前,必须先有状态上行链路
- 否则
call_manager无法确认当前是否真的存在可接听的来电对象
第 2 段:用户点击接听
- 用户在电话界面点击接听
- 上层请求进入
call_manager - 再通过
CellularCallConnection转到cellular_call - 由
CellularCallService路由到CSControl::Answer(callInfo)
第 3 段:CS 层接听决策
CSControl::Answer() 会:
- 根据
callInfo.index找到目标连接 - 检查该连接是否存在
- 检查当前是否有
HOLDING + ACTIVE的组合状态 - 必要时先挂断 holding 通话
- 如果有 active 通话或 waiting 来电,可能先做
SwitchCallRequest() - 最终在来电状态为:
CALL_STATUS_INCOMINGCALL_STATUS_ALERTINGCALL_STATUS_WAITING
时调用pConnection->AnswerRequest(slotId)
接听流程说明:
- 接听不是无条件执行
ATA - 上层已经维护了连接对象和状态机,只有状态满足条件才会向下发命令
第 4 段:底层接听执行
CellularCallConnectionCS::AnswerRequest()调用:CoreManagerInner::SetMute(..., false, ...)CoreManagerInner::Answer(...)
- 后续逐层下发到:
TelRilManagerHRilManager / HRilCallReqAnswer()
ReqAnswer()最终发送ATA
11.3 挂断调用链
第 1 段:统一挂断入口
- 用户点击挂断
- 上层请求经
call_manager进入cellular_call CellularCallService路由到CSControl::HangUp(callInfo, type)
第 2 段:CS 层按挂断类型分流
CSControl::HangUp() 不是单一路径,它会根据 CallSupplementType 分支处理:
TYPE_DEFAULTTYPE_HANG_UP_HOLD_WAITTYPE_HANG_UP_ACTIVETYPE_HANG_UP_ALL
这里反映出:
- “挂断”在 CS 领域里不只是结束当前通话
- 它还可能表示释放活动通话、释放等待通话、恢复 held 通话等 3GPP 语义
第 3 段:默认挂断
对于 TYPE_DEFAULT:
- 通过
callInfo.index找到目标连接 - 上报
DISCONNECTING - 设置断开原因
USER_TERMINATED - 调用
pConnection->HangUpRequest(slotId)
然后继续:
CellularCallConnectionCS::HangUpRequest()CoreManagerInner::Hangup(...)- 下沉到
ReqHangup()
最终 AT 命令为:
AT+CHLD=1<index>
第 4 段:拒接和挂断全部
对于拒接:
CSControl::Reject()会校验当前是否是振铃态- 设置断开原因
USER_DECLINE - 调用
pConnection->RejectRequest(slotId)
底层最终执行:
ReqReject()- AT 命令:
ATH
对于“挂断全部”:
CSControl::HangUp()会复用RejectRequest()- 这是因为代码里明确说明:挂断全部和拒接在底层命令上可以复用同一条路径
11.4 关键结论
CellularCallService是分流中枢
这一层决定请求究竟走 CS、IMS 还是卫星。CSControl是业务语义最重的一层
它不仅发请求,还承担网络制式判断、MMI 判断、连接状态判断、补充业务类型分流。CellularCallConnectionCS -> CoreManagerInner -> RIL Adapter是标准下沉链
真正和 modem 打交道的是底层,而不是call_manager或CSControl本身。
12. 术语与角色对照
为了避免阅读过程中混淆概念,这里把文中反复出现的几个术语和角色统一列一下。
| 术语 | 含义 | 在本文中的位置 |
|---|---|---|
| CS | Circuit Switched,电路域语音电话 |
本文主线 |
| IMS | 基于 IP 多媒体子系统的语音/视频通话 | CellularCallService 的另一条分流路径 |
| OTT | 互联网通话 | 由call_manager 统一管理,但不走 cellular_call -> CSControl -> RIL |
| SA | SystemAbility,系统能力服务 |
call_manager 和 cellular_call 都以 SA 形式对外提供服务 |
| handler | 异步事件处理对象 | cellular_call 和 core_service 之间的事件承载点 |
| RIL | Radio Interface Layer | 电话服务和 modem 之间的适配层 |
| HRIL | Harmony 侧的 RIL 适配实现 | ril_adapter 中的核心执行层 |
| modem | 基带/通信模组 | 最终执行拨号、接听、挂断等命令 |
再对应到具体模块:
| 模块 | 角色 | 主要职责 |
|---|---|---|
call_manager |
统一入口 | 管理呼叫对象、转发拨号/接听/挂断请求、协调音频和蓝牙等资源 |
cellular_call |
蜂窝呼叫控制层 | 负责 CS/IMS/卫星分流,维护蜂窝呼叫的业务语义 |
core_service |
电话底层服务层 | 把蜂窝呼叫请求继续下发到tel_ril |
ril_adapter |
RIL 适配层 | 屏蔽不同 modem 厂商差异,向底层发送统一请求 |
vendor/at_call.c |
厂商执行层 | 组装并发送最终的 AT 命令 |
13. 关键状态与命令映射
CS 电话链路里最容易混淆的,一类是通话状态,一类是 AT 命令。把这两类信息单独列出来,后面看调用链会更顺。
13.1 常见通话状态
结合 CSControl::Answer() 中对状态的判断,可以把常见状态理解为:
| 状态 | 含义 | 常见动作 |
|---|---|---|
CALL_STATUS_ACTIVE |
当前通话已建立 | 保持、切换、挂断 |
CALL_STATUS_HOLDING |
当前通话处于保持状态 | 恢复、释放 |
CALL_STATUS_DIALING |
主叫拨号中 | 等待网络响应 |
CALL_STATUS_ALERTING |
对端振铃中 | 等待接通或取消 |
CALL_STATUS_INCOMING |
来电中 | 接听、拒接 |
CALL_STATUS_WAITING |
呼叫等待 | 切换、接听、挂断其他通话 |
CALL_STATUS_DISCONNECTING |
断开过程中 | 等待底层释放 |
这些状态的意义不只体现在 UI 展示上,还直接影响 CSControl 是否允许继续发起拨号、接听或挂断。
13.2 关键 AT 命令
从 at_call.c 可以直接看到几个最关键的命令:
| 动作 | 入口函数 | 典型命令 | 说明 |
|---|---|---|---|
| 拨号 | ReqDial() |
ATD<number><clir>; |
发起主叫呼叫 |
| 接听 | ReqAnswer() |
ATA |
应答来电 |
| 拒接 | ReqReject() |
ATH |
结束来电或拒接 |
| 挂断 | ReqHangup() |
AT+CHLD=1<index> |
按指定连接索引挂断 |
| 获取通话列表 | ReqGetCallList() |
AT+CLCC |
查询当前通话列表 |
阅读下层链路时,可以把这张表当作“最终落点索引”。
13.3 三类动作的最终落点
把上层动作和下层命令再对齐一次:
| 上层动作 | CS 控制入口 | 底层请求 | 最终命令 |
|---|---|---|---|
| 拨号 | CSControl::Dial() |
CellularCallConnectionCS::DialRequest() |
ATD |
| 接听 | CSControl::Answer() |
CellularCallConnectionCS::AnswerRequest() |
ATA |
| 挂断 | CSControl::HangUp() |
CellularCallConnectionCS::HangUpRequest() |
AT+CHLD |
| 拒接 | CSControl::Reject() |
CellularCallConnectionCS::RejectRequest() |
ATH |
14. 典型排查路径
如果后续要继续看源码、做问题定位或者写调试笔记,这一节会比较有用。
14.1 拨号失败怎么查
建议按下面顺序排查:
- 先看
call_manager是否真的把请求转给了cellular_call - 再看
CellularCallService::DialNormalCall()最终走的是CSControl还是IMSControl - 再看
CSControl::Dial()是否因为注册态、网络类型、MMI 或当前通话状态提前返回 - 再看
CellularCallConnectionCS::DialRequest()是否成功调用了CoreManagerInner::Dial() - 最后看
HRilCall::Dial()和ReqDial()是否真正下发了ATD
换成源码入口,大致就是:
call_manager/services/call/src/call_request_process.cppcellular_call/services/manager/src/cellular_call_service.cppcellular_call/services/control/src/cs_control.cppcellular_call/services/connection/src/cellular_call_connection_cs.cppcore_service/frameworks/native/src/core_manager_inner.cppril_adapter/services/hril/src/hril_call.cppril_adapter/services/vendor/src/at_call.c
14.2 接听失败怎么查
接听失败一般重点看三类条件:
call_manager是否已经收到来电状态CSControl::Answer()是否找到了正确的连接对象- 当前状态是否满足接听条件
常见排查点:
CellularCallRegister::ReportSingleCallInfo()是否已经把来电状态回报给call_managercallInfo.index是否能正确匹配到connectionMap_中的连接- 当前是否存在
ACTIVE + HOLDING组合,导致需要先切换或释放 ReqAnswer()是否最终发送了ATA
14.3 挂断或拒接异常怎么查
挂断类问题通常分两类:
- 目标连接没有找到
- 找到了连接,但最终命令没有正常下发
建议关注:
CSControl::HangUp()是否按CallSupplementType走到了预期分支CSControl::Reject()是否确认当前来电处于振铃态HangUpRequest()是否正确带上了indexReqHangup()是否生成了正确的AT+CHLD=1<index>ReqReject()是否真的发送了ATH
14.4 看日志时优先关注哪几层
如果日志很多,建议按层次抓主线:
| 优先级 | 层次 | 主要看什么 |
|---|---|---|
| 1 | CellularCallService |
是否分流到CSControl |
| 2 | CSControl |
是否被状态、注册态或网络类型拦截 |
| 3 | CellularCallConnectionCS |
是否成功进入core_service |
| 4 | CoreManagerInner / TelRilManager |
是否成功把请求转给底层 |
| 5 | HRilCall / at_call.c |
最终命令是否真正发出 |
15. 常见误区与 FAQ
15.1 常见误区
误区 1:call_manager 就是最终的拨号执行层
不是。
call_manager 更像统一入口和统一管理层。真正的蜂窝呼叫执行路径要继续往下走到:
cellular_callcore_serviceril_adaptervendor/at_call.c
误区 2:CS 和 IMS 的差异只体现在底层 modem
也不是。
从当前代码可以看到,分流点在 CellularCallService::DialNormalCall() 就已经出现了。也就是说,CS 和 IMS 的分叉不是到最底层才出现,而是在蜂窝呼叫服务层就已经决定。
误区 3:接听就是直接发 ATA
不完全是。
在 ATA 发出去之前,CSControl::Answer() 还会检查:
- 连接对象是否存在
- 当前是否有
ACTIVE + HOLDING - 当前状态是不是
INCOMING / ALERTING / WAITING
因此接听前面还有一层比较完整的状态机判断。
误区 4:挂断只有一条路径
不是。
CSControl::HangUp() 会根据 CallSupplementType 分不同分支处理。对 CS 来说,挂断不只是“结束当前通话”,还带有保持、切换、释放等待通话这类补充业务语义。
15.2 FAQ
Q1:为什么文章里一直强调 CellularCallService?
因为它是整个蜂窝呼叫域的入口和分流点。只要这一层没理清,后面的 CSControl、IMSControl、卫星分支都容易混在一起。
Q2:为什么 CSControl 很关键?
因为这层不只是简单转发。网络类型、注册态、MMI、连接状态、补充业务类型,都在这一层做判断。很多问题不是底层 RIL 出错,而是在这里就已经被挡掉了。
Q3:为什么文档里同时写了 core_service 和 ril_adapter?
因为它们职责不同:
core_service负责电话服务内部的继续分发ril_adapter负责面向 modem 的统一适配
从阅读链路上看,它们虽然都在下游,但不属于同一层。
Q4:如果只想快速抓主线,最少看哪几个文件?
最少先看这 6 个文件:
call_manager/services/call/src/call_request_process.cppcellular_call/services/manager/src/cellular_call_service.cppcellular_call/services/control/src/cs_control.cppcellular_call/services/connection/src/cellular_call_connection_cs.cppcore_service/frameworks/native/src/core_manager_inner.cppril_adapter/services/vendor/src/at_call.c
把这 6 个文件串起来,主链路基本就清楚了。
16. 实战定位示例
前面的内容偏结构化分析,这里补一个更贴近实际排查的例子。
16.1 例子:拨号请求发出去了,但电话没有打出去
假设现象是:
- 上层界面已经点击拨号
call_manager看起来也收到了请求- 但设备没有真正拨出电话
这种问题,建议按下面顺序看:
第 1 步:确认请求有没有从 call_manager 发出去
先看:
CallRequestProcess::HandleStartDial()CellularCallConnection::Dial()
这里要确认两件事:
CellularCallInfo是否封装成功cellular_call的 IPC 调用是否真的发起
如果这一步就失败,问题通常还停留在上层参数或 IPC 连接阶段。
第 2 步:确认 cellular_call 最终走的是不是 CS
再看:
CellularCallService::Dial()CellularCallService::DialNormalCall()
要确认:
- 是否走到了
CSControl - 是否被分流到了
IMSControl或其他分支
如果文章主线分析的是 CS,但实际请求没有进入 CSControl,那后面沿着 CS 链路继续查就会跑偏。
第 3 步:确认是不是被 CSControl 提前拦截
重点看:
GetCsRegState(slotId)GetNetworkStatus(slotId)IsNeedExecuteMMI(...)CanCall(connectionMap_)
这一步常见的失败原因包括:
- 当前不在 CS 域服务中
- 网络类型不是 GSM/CDMA
- 号码被识别成 MMI
- 当前已有通话状态,不允许继续发起新呼叫
很多“拨号失败”其实并不是 RIL 没工作,而是在 CSControl 里已经返回了错误。
第 4 步:确认是否真正进入 core_service
如果 CSControl 已经过了,就继续看:
CellularCallConnectionCS::DialRequest()CoreManagerInner::Dial()
这里主要确认:
handler是否取到CoreManagerInner::Dial()是否实际被调用telRilManager_是否为空
走不到这里,问题还在 cellular_call 层;走到了这里,问题已经进入电话底层服务链路。
第 5 步:确认底层命令有没有真正发出
最后看:
HRilManager::Dial()HRilCall::Dial()ReqDial()
最关键的是确认:
RequestVendor(...)是否成功ReqDial()是否生成了ATD<number><clir>;SendCommandLock()是否真正把命令发给 modem
如果 ATD 根本没发出,问题就在 hril 或 vendor 层。
16.2 这个例子背后的定位方法
这个例子真正想说明的是,查电话问题不能只盯某一个模块,而要按层次走:
- 先确认请求有没有发出
- 再确认有没有被分流
- 再确认有没有在业务层被拦截
- 最后再看底层命令有没有真正落下去
这也是本文前面整条主链路分析的实际用途。
17. 关键源码索引
源码阅读顺序如下:
17.1 入口和总控
base/telephony/call_manager/services/call_manager_service/src/call_manager_service.cppbase/telephony/call_manager/services/call/src/call_request_process.cppbase/telephony/call_manager/services/telephony_interaction/src/cellular_call_connection.cpp
17.2 CS/IMS 分流与 CS 核心控制
base/telephony/cellular_call/services/manager/src/cellular_call_service.cppbase/telephony/cellular_call/services/control/src/cs_control.cppbase/telephony/cellular_call/services/connection/src/cellular_call_connection_cs.cpp
17.3 事件处理与回调回传
base/telephony/cellular_call/services/manager/src/cellular_call_handler.cppbase/telephony/cellular_call/services/manager/src/cellular_call_register.cppbase/telephony/cellular_call/services/manager/src/cellular_call_stub.cpp
17.4 下层 RIL 链路
base/telephony/core_service/frameworks/native/src/core_manager_inner.cppbase/telephony/core_service/services/tel_ril/src/tel_ril_manager.cppbase/telephony/ril_adapter/services/hril/src/hril_manager.cppbase/telephony/ril_adapter/services/hril/src/hril_call.cppbase/telephony/ril_adapter/services/vendor/src/at_call.c
18. 一句话总结
当前电话子系统中的 CS 电话实现可以概括为:
由 call_manager 统一受理呼叫请求,由 cellular_call 在蜂窝域内决定走 CSControl,再通过 core_service 和 ril_adapter 层层下发,最终在 vendor AT 层通过传统 AT 命令驱动 modem 完成真实拨号和通话控制。
更多推荐

所有评论(0)