电话子系统 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_managercellular_callcore_serviceril_adapter 的边界还不够清楚的人
  • 需要排查拨号、接听、挂断问题,想先建立一套定位路径的人
  • 需要做内部分享、知识传递或二次整理的人

1.2 阅读收益

读完这篇文章,至少可以拿到下面几项信息:

  • 知道 CS 电话请求是怎么从上层一路走到 modem 的
  • 知道状态和事件是怎么从底层一路回到 call_manager
  • 知道 CS、IMS、OTT 三类通话在当前代码树里的边界
  • 知道排查问题时应该先看哪几个模块、哪几个文件

1.3 阅读建议

如果是第一次看这个方向,建议按下面的顺序阅读:

  1. 先看“总体结论”,建立主链路印象
  2. 再看“模块边界”和“请求下行链路”,弄清楚分层
  3. 然后看“时序图”和“详细调用链”,把请求和状态串起来
  4. 最后看“排查路径”“误区与 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_callcore_service 协作

相关代码:

  • call_manager/README.md
  • call_manager/services/call_manager_service/src/call_manager_service.cpp
  • call_manager/services/call/src/call_request_process.cpp
  • call_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:控制层,包含 CSControlIMSControl
  • connection:连接层,把控制请求下发给 core_service

相关代码:

  • cellular_call/README.md
  • cellular_call/services/manager/src/cellular_call_service.cpp
  • cellular_call/services/control/src/cs_control.cpp
  • cellular_call/services/connection/src/cellular_call_connection_cs.cpp
  • cellular_call/services/manager/src/cellular_call_handler.cpp
  • cellular_call/services/manager/src/cellular_call_register.cpp

3.3 core_service

core_service 在 CS 实现中起到“把通话控制请求继续下发给 RIL”的作用。

关键路径:

  • core_service/frameworks/native/src/core_manager_inner.cpp
  • core_service/services/tel_ril/src/tel_ril_manager.cpp

3.4 ril_adapter

ril_adapter 的职责是:

  • 屏蔽不同 modem 厂商实现差异
  • 对 telephony 服务层提供统一接口
  • 通过 HDF 服务和底层厂商实现通信

关键路径:

  • ril_adapter/README.md
  • ril_adapter/services/hril/src/hril_manager.cpp
  • ril_adapter/services/hril/src/hril_call.cpp
  • ril_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_servicecellular_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() 会:

  1. 把上层拨号参数封装成 CellularCallInfo
  2. 调用 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 桥接。

主要行为:

  1. 通过系统能力管理器获取 cellular_call 远程对象
  2. 转换为 CellularCallInterface
  3. 注册 ICallStatusCallback
  4. 调用远程接口 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() 是整个蜂窝呼叫域的总分发入口。

分流逻辑如下:

  1. 先校验 slot
  2. 判断是否处于 SRVCC 特殊状态
  3. 判断是否是紧急号码
  4. 如果卫星电话打开,走 SatelliteControl
  5. 否则判断是否需要 IMS
  6. 如果需要 IMS,走 IMSControl
  7. 否则走 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() 的主要处理步骤如下:

  1. 记录 HiSysEvent 参数
  2. 执行拨号前置校验 DialPreJudgment
  3. 查询 CS 注册状态 GetCsRegState
  4. 如果不在服务中且不是紧急电话,则返回失败
  5. 查询当前网络类型 GetNetworkStatus
  6. 按网络类型进入:
    • 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

  • phoneNum
  • clirMode

然后:

  1. 调用 CellularCallService::SetMute(slotId, false)
  2. 实例化 CellularCallConnectionCS
  3. 调用 csConnection.DialRequest(slotId, dialRequest)

关键代码:

  • cellular_call/services/control/src/cs_control.cpp

从职责划分看:

  • CSControl 负责业务判断
  • 真正的“下沉请求”由 CellularCallConnectionCS 完成

5.8 CellularCallConnectionCScore_service 下发请求

CellularCallConnectionCS::DialRequest() 会:

  1. 获取 CellularCallService 实例
  2. 获取对应 slot 的 handler
  3. 更新号码信息
  4. 调用 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() 的逻辑很直接:

  1. 检查 telRilManager_
  2. 创建对应 InnerEvent
  3. handler 绑定给 response
  4. 调用 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() 会:

  1. 构造 HRilDial
  2. 把地址和 CLIR 填进去
  3. 调用 RequestVendor(...)

关键代码:

  • ril_adapter/services/hril/src/hril_manager.cpp
  • ril_adapter/services/hril/src/hril_call.cpp

5.12 at_call.c 最终执行 AT 命令

在当前可见仓内,拨号最终落到:

  • ril_adapter/services/vendor/src/at_call.c

ReqDial() 的核心逻辑是:

  1. 根据 clir 组装 CLIR 参数
  2. 生成 AT 命令
  3. 命令格式为:
ATD<number><clir>;
  1. 通过 SendCommandLock() 发给 modem
  2. 回传拨号结果

同文件中还能看到:

  • 挂断:AT+CHLD
  • 接听:通常对应 ATA
  • 拒接/结束:通常对应 ATH 或相关补充命令

结合当前仓内代码,可以确认:

  • CS 呼叫最终是通过传统 AT 命令方式落到 modem 的

6. CS 电话状态上行链路

CS 电话不仅有请求下发,还有状态和事件的上行回报。

6.1 cellular_callcall_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.cpp
  • cellular_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 选择转给 IMSControlCSControl
  • 对于 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_GSM
  • PHONE_TYPE_IS_CDMA

这表明 CS 层不是单一路径,而是根据接入网络执行不同控制逻辑。

7.2 依赖 CS 域注册状态

CSControl::Dial() 会检查:

  • GetCsRegState(slotId)

如果设备不在 CS 域服务中,且当前不是紧急号码,则拨号失败。

7.3 支持 MMI 码

DialGsm() / DialCdma() 中,都会先调用:

  • IsNeedExecuteMMI(...)

这表明星号井号类号码并不总是普通拨号,它们可能转为 MMI 补充业务处理。

7.4 支持多路通话和补充业务

CS 侧支持的操作不仅有:

  • 拨号
  • 接听
  • 拒接
  • 挂断

还包括:

  • 保持/恢复
  • 交换通话
  • 合并会议
  • 拆分会议
  • DTMF
  • 补充业务

这些能力在 CellularCallConnectionCSCSControl 中都有明确入口。

8. CS 与 IMS/OTT 的关系

8.1 在 call_manager 看,三者都是被统一管理的呼叫类型

call_manager/README.md 明确说明:

  • call_manager 同时管理 CSIMSOTT

从顶层呼叫管理角度看,它们都属于统一的电话/通话对象管理范围。

8.2 在 cellular_call 看,CS 和 IMS 是蜂窝电话内部的两条实现分支

cellular_call/README.mdCellularCallService::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. 培训视角下的总览图

这一部分先把整体关系铺开,便于后面再落到具体源码。

这一部分按下面顺序看会更顺:

  1. 先讲谁负责接请求
  2. 再讲谁决定走 CS 还是 IMS
  3. 再讲谁真正把请求送到 modem
  4. 最后讲状态是怎么逐层回来的

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() 封装 CellularCallInfo
  • DelayedSingleton<CellularCallConnection>::GetInstance()->Dial(callInfo) 发起跨进程调用

这一段对应统一入口层:

  • call_manager 只做统一调度和参数封装
  • 真正的蜂窝呼叫执行不在 call_manager 内部完成

第 2 段:蜂窝呼叫总分流

  • CellularCallConnection::Dial() 调用远端 cellular_call
  • CellularCallService::Dial() 做总体校验
  • CellularCallService::DialNormalCall() 决定走 IMSControl 还是 CSControl
  • 对于普通 CS 呼叫,进入 CSControl::Dial(callInfo, isEcc)

这一段对应蜂窝呼叫总分流:

  • 这是整个电话子系统最关键的分流点
  • 后续是否走 CS,取决于这一层的判断结果

第 3 段:CS 业务控制

  • CSControl::Dial() 先做拨号前校验
  • 检查 GetCsRegState(slotId),确认 CS 域可用
  • 检查 GetNetworkStatus(slotId),区分 GSMCDMA
  • 分别进入:
    • DialGsm()
    • DialCdma()

这一段对应 CS 业务控制:

  • CS 拨号并不是“一个通用拨号函数直接到底”
  • 它先依赖网络状态、注册状态和当前呼叫状态做业务判断

第 4 段:GSM/CDMA 分支处理

DialGsm() 主要做:

  • 号码标准化
  • MMI 判断
  • 通话状态校验
  • 如果已有 active 通话,则先 SwitchCallRequest()
  • 最终调用 EncapsulateDialCommon()

DialCdma() 主要做:

  • 号码标准化
  • MMI 判断
  • 通话状态校验
  • 如果已有 active 通话,则走 SendCDMAThreeWayDialRequest()
  • 否则继续 EncapsulateDialCommon()

这里的结论是:

  • GSM 和 CDMA 的行为差异,不在最底层,而是在 CSControl 就已经开始体现

第 5 段:下沉到底层

  • EncapsulateDialCommon() 组装 DialRequestStruct
  • CellularCallConnectionCS::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_manager
  • call_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_INCOMING
    • CALL_STATUS_ALERTING
    • CALL_STATUS_WAITING
      时调用 pConnection->AnswerRequest(slotId)

接听流程说明:

  • 接听不是无条件执行 ATA
  • 上层已经维护了连接对象和状态机,只有状态满足条件才会向下发命令

第 4 段:底层接听执行

  • CellularCallConnectionCS::AnswerRequest() 调用:
    • CoreManagerInner::SetMute(..., false, ...)
    • CoreManagerInner::Answer(...)
  • 后续逐层下发到:
    • TelRilManager
    • HRilManager / HRilCall
    • ReqAnswer()
  • ReqAnswer() 最终发送 ATA

11.3 挂断调用链

第 1 段:统一挂断入口

  • 用户点击挂断
  • 上层请求经 call_manager 进入 cellular_call
  • CellularCallService 路由到 CSControl::HangUp(callInfo, type)

第 2 段:CS 层按挂断类型分流

CSControl::HangUp() 不是单一路径,它会根据 CallSupplementType 分支处理:

  • TYPE_DEFAULT
  • TYPE_HANG_UP_HOLD_WAIT
  • TYPE_HANG_UP_ACTIVE
  • TYPE_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_managerCSControl 本身。

12. 术语与角色对照

为了避免阅读过程中混淆概念,这里把文中反复出现的几个术语和角色统一列一下。

术语 含义 在本文中的位置
CS Circuit Switched,电路域语音电话 本文主线
IMS 基于 IP 多媒体子系统的语音/视频通话 CellularCallService 的另一条分流路径
OTT 互联网通话 call_manager 统一管理,但不走 cellular_call -> CSControl -> RIL
SA SystemAbility,系统能力服务 call_managercellular_call 都以 SA 形式对外提供服务
handler 异步事件处理对象 cellular_callcore_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 拨号失败怎么查

建议按下面顺序排查:

  1. 先看 call_manager 是否真的把请求转给了 cellular_call
  2. 再看 CellularCallService::DialNormalCall() 最终走的是 CSControl 还是 IMSControl
  3. 再看 CSControl::Dial() 是否因为注册态、网络类型、MMI 或当前通话状态提前返回
  4. 再看 CellularCallConnectionCS::DialRequest() 是否成功调用了 CoreManagerInner::Dial()
  5. 最后看 HRilCall::Dial()ReqDial() 是否真正下发了 ATD

换成源码入口,大致就是:

  • call_manager/services/call/src/call_request_process.cpp
  • cellular_call/services/manager/src/cellular_call_service.cpp
  • cellular_call/services/control/src/cs_control.cpp
  • cellular_call/services/connection/src/cellular_call_connection_cs.cpp
  • core_service/frameworks/native/src/core_manager_inner.cpp
  • ril_adapter/services/hril/src/hril_call.cpp
  • ril_adapter/services/vendor/src/at_call.c

14.2 接听失败怎么查

接听失败一般重点看三类条件:

  1. call_manager 是否已经收到来电状态
  2. CSControl::Answer() 是否找到了正确的连接对象
  3. 当前状态是否满足接听条件

常见排查点:

  • CellularCallRegister::ReportSingleCallInfo() 是否已经把来电状态回报给 call_manager
  • callInfo.index 是否能正确匹配到 connectionMap_ 中的连接
  • 当前是否存在 ACTIVE + HOLDING 组合,导致需要先切换或释放
  • ReqAnswer() 是否最终发送了 ATA

14.3 挂断或拒接异常怎么查

挂断类问题通常分两类:

  • 目标连接没有找到
  • 找到了连接,但最终命令没有正常下发

建议关注:

  • CSControl::HangUp() 是否按 CallSupplementType 走到了预期分支
  • CSControl::Reject() 是否确认当前来电处于振铃态
  • HangUpRequest() 是否正确带上了 index
  • ReqHangup() 是否生成了正确的 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_call
  • core_service
  • ril_adapter
  • vendor/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

因为它是整个蜂窝呼叫域的入口和分流点。只要这一层没理清,后面的 CSControlIMSControl、卫星分支都容易混在一起。

Q2:为什么 CSControl 很关键?

因为这层不只是简单转发。网络类型、注册态、MMI、连接状态、补充业务类型,都在这一层做判断。很多问题不是底层 RIL 出错,而是在这里就已经被挡掉了。

Q3:为什么文档里同时写了 core_serviceril_adapter

因为它们职责不同:

  • core_service 负责电话服务内部的继续分发
  • ril_adapter 负责面向 modem 的统一适配

从阅读链路上看,它们虽然都在下游,但不属于同一层。

Q4:如果只想快速抓主线,最少看哪几个文件?

最少先看这 6 个文件:

  • call_manager/services/call/src/call_request_process.cpp
  • cellular_call/services/manager/src/cellular_call_service.cpp
  • cellular_call/services/control/src/cs_control.cpp
  • cellular_call/services/connection/src/cellular_call_connection_cs.cpp
  • core_service/frameworks/native/src/core_manager_inner.cpp
  • ril_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 根本没发出,问题就在 hrilvendor 层。

16.2 这个例子背后的定位方法

这个例子真正想说明的是,查电话问题不能只盯某一个模块,而要按层次走:

  1. 先确认请求有没有发出
  2. 再确认有没有被分流
  3. 再确认有没有在业务层被拦截
  4. 最后再看底层命令有没有真正落下去

这也是本文前面整条主链路分析的实际用途。

17. 关键源码索引

源码阅读顺序如下:

17.1 入口和总控

  • base/telephony/call_manager/services/call_manager_service/src/call_manager_service.cpp
  • base/telephony/call_manager/services/call/src/call_request_process.cpp
  • base/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.cpp
  • base/telephony/cellular_call/services/control/src/cs_control.cpp
  • base/telephony/cellular_call/services/connection/src/cellular_call_connection_cs.cpp

17.3 事件处理与回调回传

  • base/telephony/cellular_call/services/manager/src/cellular_call_handler.cpp
  • base/telephony/cellular_call/services/manager/src/cellular_call_register.cpp
  • base/telephony/cellular_call/services/manager/src/cellular_call_stub.cpp

17.4 下层 RIL 链路

  • base/telephony/core_service/frameworks/native/src/core_manager_inner.cpp
  • base/telephony/core_service/services/tel_ril/src/tel_ril_manager.cpp
  • base/telephony/ril_adapter/services/hril/src/hril_manager.cpp
  • base/telephony/ril_adapter/services/hril/src/hril_call.cpp
  • base/telephony/ril_adapter/services/vendor/src/at_call.c

18. 一句话总结

当前电话子系统中的 CS 电话实现可以概括为:

call_manager 统一受理呼叫请求,由 cellular_call 在蜂窝域内决定走 CSControl,再通过 core_serviceril_adapter 层层下发,最终在 vendor AT 层通过传统 AT 命令驱动 modem 完成真实拨号和通话控制。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐