Android Telephony通话状态更新消息上报流程

1 阅读8分钟

我来详细分析Android Telephony中通话状态更新消息的上报流程。

现在让我查看更多AT指令的处理和上报流程:

现在让我查看更多关于AT命令处理和轮询的细节:

现在让我查看frameworks层的处理,以了解完整的上报流程:

基于我对代码的分析,这个流程涉及多个层次的交互,我将详细讲解从Modem到上层应用的完整路径。

整体架构概览

通话状态更新的上报流程包括以下主要环节:

Modem → HAL → RIL Framework → HIDL Service → Java Framework → Phone/CallTracker → Application

第一阶段:Modem端的主动上报

1.1 Modem主动上报(Unsolicited Response)

Modem在通话状态发生变化时,主动向基带驱动发送未经请求的回复信息。常见的主动上报包括:

  • RING: 来电铃声指示
  • +CRING: GSM特定的铃声格式(如"VOICE")
  • NO CARRIER: 通话结束,载波消失
  • +CCWA: 来电等待指示
  • +CREG: 网络注册状态变化
  • +CGREG: GPRS网络注册状态变化
// reference-ril.c: 3490-3497 行
// 当Modem发来上述指示时,Reference RIL解析并上报
} else if (strStartsWith(s,"+CRING:")
            || strStartsWith(s,"RING")
            || strStartsWith(s,"NO CARRIER")
            || strStartsWith(s,"+CCWA")
) {
    RIL_onUnsolicitedResponse (
        RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,
        NULL, 0);
}

第二阶段:Vendor RIL处理

2.1 Vendor RIL的AT指令处理

Vendor RIL运行在独立的进程中,通过以下步骤处理Modem的主动上报:

  1. AT Reader线程 持续从Modem读取数据
  2. 未经请求的响应处理 (onUnsolicitedResponse回调)
  3. 调用RIL_onUnsolicitedResponse将消息上报给RIL Framework
// reference-ril.c: 3490-3497
static void onUnsolicited (const char *s, const char *sms_pdu)
{
    // ... 解析AT指令 ...
    } else if (strStartsWith(s,"+CRING:")
                || strStartsWith(s,"RING")
                || strStartsWith(s,"NO CARRIER")
                || strStartsWith(s,"+CCWA")
    ) {
        RIL_onUnsolicitedResponse (
            RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,
            NULL, 0);
    }
}

第三阶段:RIL Framework处理

3.1 RIL_onUnsolicitedResponse函数

这是RIL Framework的核心处理函数,在libril/ril.cpp中实现:

// ril.cpp: 722-729 行
extern "C"
void RIL_onUnsolicitedResponse(int unsolResponse, const void *data,
                                size_t datalen, RIL_SOCKET_ID socket_id)

主要处理步骤:

  1. Wakelock管理 - 根据消息类型获取Partial Wakelock,防止CPU休眠

    // ril.cpp: 757-761 行
    switch (s_unsolResponses[unsolResponseIndex].wakeType) {
        case WAKE_PARTIAL:
            grabPartialWakeLock();
            shouldScheduleTimeout = true;
        break;
    }
    
  2. HIDL指示分发 - 通过HIDL接口将消息转发给上层

    // ril.cpp: 793-797 行
    if (s_unsolResponses[unsolResponseIndex].responseFunction) {
        ret = s_unsolResponses[unsolResponseIndex].responseFunction(
                (int) soc_id, responseType, 0, RIL_E_SUCCESS, 
                const_cast<void*>(data), datalen);
    }
    
  3. Wakelock释放 - 设置超时后释放Wakelock(默认200ms)

3.2 未经请求响应映射表

ril_unsol_commands.h中定义了消息类型到处理函数的映射:

// ril_unsol_commands.h: 17-19 行
{RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, radio::radioStateChangedInd, WAKE_PARTIAL},
{RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, radio::callStateChangedInd, WAKE_PARTIAL},
{RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, radio::networkStateChangedInd, WAKE_PARTIAL},

第四阶段:HIDL服务处理

4.1 callStateChangedInd函数

ril_service.cpp中实现HIDL指示处理:

// ril_service.cpp: 6758-6773 行
int radio::callStateChangedInd(int slotId,
                               int indicationType, int token, RIL_Errno e, void *response,
                               size_t responseLen) {
    if (radioService[slotId] != NULL && radioService[slotId]->mRadioIndication != NULL) {
        Return<void> retStatus = radioService[slotId]->mRadioIndication->callStateChanged(
                convertIntToRadioIndicationType(indicationType));
        radioService[slotId]->checkReturnStatus(retStatus);
    }
    return 0;
}

关键点:

  • 获取对应Slot的HIDL Indication实例
  • 调用HIDL接口的callStateChanged方法
  • 将消息转发给Java层

第五阶段:Java Framework处理

5.1 VoiceIndication接收

VoiceIndication.javacallStateChanged方法中处理:

// VoiceIndication.java: 94-99 行
public void callStateChanged(int indicationType) {
    mRil.processIndication(HAL_SERVICE_VOICE, indicationType);

    if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);

    mRil.mCallStateRegistrants.notifyRegistrants();
}

处理流程:

  1. 处理指示类型(标记为已处理)
  2. 记录日志
  3. 通知所有已注册的监听者

5.2 CallStateRegistrants通知

mCallStateRegistrants是一个RegistrantList,包含所有对通话状态变化感兴趣的监听者(如GsmCdmaCallTracker):

// BaseCommands.java: 292-294 行
@Override
public void registerForCallStateChanged(Handler h, int what, Object obj) {
    mCallStateRegistrants.addUnique(h, what, obj);
}

GsmCdmaCallTracker在初始化时注册:

// GsmCdmaCallTracker.java: 160-171 行
public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
    // ...
    mCi = phone.mCi;
    mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
    // ...
}

第六阶段:CallTracker轮询当前通话列表

6.1 触发getCurrentCalls请求

当接收到EVENT_CALL_STATE_CHANGE消息时,GsmCdmaCallTracker不会直接使用该消息,而是主动轮询当前的通话列表:

// CallTracker.java: 91-98 行
protected void pollCallsWhenSafe() {
    mNeedsPoll = true;

    if (checkNoOperationsPending()) {
        mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
        mCi.getCurrentCalls(mLastRelevantPoll);
    }
}

为什么需要轮询?

  • Modem上报的只是"状态有变化"的信号,不包含具体的通话列表数据
  • 必须通过AT+CLCC命令主动查询当前的通话详情

6.2 RIL.getCurrentCalls请求

// RIL.java: 1640-1655 行
@Override
public void getCurrentCalls(Message result) {
    RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
    if (!canMakeRequest("getCurrentCalls", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
        return;
    }

    RILRequest rr = obtainRequest(RIL_REQUEST_GET_CURRENT_CALLS, result, mRILDefaultWorkSource);

    if (RILJ_LOGD) {
        riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
    }

    radioServiceInvokeHelper(HAL_SERVICE_VOICE, rr, "getCurrentCalls", () -> {
        voiceProxy.getCurrentCalls(rr.mSerial);
    });
}

第七阶段:解析通话列表

7.1 Modem响应getCurrentCalls

Modem响应AT+CLCC命令,返回当前通话列表。每条通话的格式为:

+CLCC: <id>,<dir>,<stat>,<bearer>,<mpty>[,<number>,<type>[,<alpha>]]

例如:

+CLCC: 1,0,2,0,0,"10086",129
+CLCC: 2,1,2,0,0

7.2 handlePollCalls处理结果

当RIL返回通话列表后,handlePollCallsGsmCdmaCallTracker中处理:

// GsmCdmaCallTracker.java: 848-863 行
protected synchronized void handlePollCalls(AsyncResult ar) {
    List polledCalls;

    if (VDBG) log("handlePollCalls");
    if (ar.exception == null) {
        polledCalls = (List)ar.result;  // DriverCall列表
    } else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
        polledCalls = new ArrayList();
    } else {
        pollCallsAfterDelay();
        return;
    }
    
    // ... 处理通话列表 ...
}

第八阶段:更新通话状态和通知上层

8.1 遍历并更新通话对象

// GsmCdmaCallTracker.java: 878-1070 行
for (int i = 0, curDC = 0, dcSize = polledCalls.size(); i < mConnections.length; i++) {
    GsmCdmaConnection conn = mConnections[i];
    DriverCall dc = null;
    
    // 获取Modem返回的通话信息
    if (curDC < dcSize) {
        dc = (DriverCall) polledCalls.get(curDC);
        if (dc.index == i+1) {
            curDC++;
        } else {
            dc = null;
        }
    }
    
    // 比较本地记录和Modem状态
    if (conn == null && dc != null) {
        // 新通话出现 - 创建新的GsmCdmaConnection
        mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);
        newRinging = checkMtFindNewRinging(dc, i);
    } else if (conn != null && dc == null) {
        // 通话已被删除 - 标记为已丢弃
        mDroppedDuringPoll.add(conn);
    } else if (conn != null && dc != null) {
        // 通话存在,更新其状态
        boolean changed = conn.update(dc);
        hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
    }
}

8.2 更新Phone状态

// GsmCdmaCallTracker.java: 1201-1202 行
updatePhoneState();

这个函数根据通话列表确定Phone的总体状态(IDLE、RINGING、OFFHOOK)。

8.3 通知监听者

// GsmCdmaCallTracker.java: 1215-1218 行
if (hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) {
    mPhone.notifyPreciseCallStateChanged();
    updateMetrics(mConnections);
}

第九阶段:上层应用感知

9.1 Phone层的通知

// GsmCdmaCallTracker.java: 1117 行
if (newRinging != null) {
    mPhone.notifyNewRingingConnection(newRinging);
}

通知Phone有新的来电。

9.2 应用层感知

应用程序通过以下方式感知通话状态变化:

  1. TelephonyCallback监听 (推荐方式)
  2. PhoneStateListener (已废弃)
  3. TelephonyRegistry (系统级通知)
// 应用层代码示例
TelephonyManager tm = context.getSystemService(TelephonyManager.class);
tm.registerTelephonyCallback(executor, new TelephonyCallback.CallStateListener() {
    @Override
    public void onCallStateChanged(int state) {
        // 处理通话状态变化
    }
});

完整的消息流时序图

时间轴 ────────────────────────────────────────────────────────────────
│
├─ [1] Modem检测到通话状态变化
│       └─→ 发送未经请求的回复 (RING, NO CARRIER, etc.)
│
├─ [2] Vendor RIL接收AT指示
│       └─→ 调用 RIL_onUnsolicitedResponse()
│
├─ [3] RIL Framework处理
│       ├─→ 获取Partial Wakelock
│       └─→ 调用HIDL response handler (callStateChangedInd)
│
├─ [4] HIDL Service转发
│       └─→ RadioIndication.callStateChanged() (AIDL)
│
├─ [5] Java Framework接收
│       └─→ VoiceIndication.callStateChanged()
│           └─→ RIL.mCallStateRegistrants.notifyRegistrants()
│
├─ [6] CallTracker感知状态变化
│       └─→ EVENT_CALL_STATE_CHANGE handler
│           └─→ pollCallsWhenSafe()
│               └─→ RIL.getCurrentCalls() [同步轮询]
│
├─ [7] Modem返回通话列表
│       └─→ AT+CLCC响应 (多条+CLCC行)
│
├─ [8] GsmCdmaCallTracker处理
│       ├─→ EVENT_POLL_CALLS_RESULT handler
│       ├─→ handlePollCalls(AsyncResult ar)
│       ├─→ 比较并更新mConnections[]数组
│       ├─→ updatePhoneState()
│       ├─→ mPhone.notifyPreciseCallStateChanged()
│       └─→ mPhone.notifyNewRingingConnection()
│
├─ [9] Phone层通知
│       └─→ TelephonyRegistry广播
│           └─→ 通知系统服务和应用
│
└─ [10] 应用层获得通话状态更新
        └─→ TelephonyCallback.onCallStateChanged()

关键数据结构

1. RIL_Call 结构体

定义单个通话的状态信息:

// ril.h
typedef struct {
    int index;              // 通话索引
    int isMT;               // 1=来电, 0=去电
    RIL_CallState state;    // 通话状态 (ACTIVE/HOLDING/DIALING/等)
    int mode;               // 通话模式 (0=语音, 1=数据, 等)
    int mpty;               // 0=非多方通话, 1=多方通话
    // ... 其他字段 ...
} RIL_Call;

2. DriverCall 结构体(Java端)

public class DriverCall {
    public enum State {
        ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING
    }
    
    public int index;
    public boolean isMT;       // Mobile Terminated (incoming)
    public State state;
    public int number;
    public int type;
    // ... 其他字段 ...
}

3. GsmCdmaConnection 类

代表一条逻辑通话连接,维护其状态、时间戳等信息。


Wakelock机制

为了防止CPU因接收Unsolicited Response而过度休眠/唤醒,RIL Framework采用了Wakelock机制:

  1. Unsolicited Response到达时 → 获取Partial Wakelock
  2. 消息处理完成后 → 设置200ms超时
  3. 超时后 → 释放Wakelock,允许CPU休眠
// ril.cpp: 700-718 行
static void wakeTimeoutCallback (void *param) {
    if (s_callbacks.version >= 13) {
        if (param == NULL) {
            s_wakelock_count = 0;
            release_wake_lock(ANDROID_WAKE_LOCK_NAME);
        }
    }
}

性能优化点

  1. Partial Wakelock - 只唤醒CPU,不点亮屏幕
  2. 轮询延迟 - 对于未完成状态(DIALING/ALERTING/INCOMING/WAITING),设置轮询延迟(通常500ms)
  3. 去重机制 - 冗余或重复的通话状态消息被容忍(如文档中所说)
  4. 异步处理 - 所有操作都在RIL事件循环中异步进行

异常处理

当Radio不可用时:

// GsmCdmaCallTracker.java: 854-857 行
if (ar.exception == null) {
    polledCalls = (List)ar.result;
} else if (isCommandExceptionRadioNotAvailable(ar.exception)) {
    polledCalls = new ArrayList();  // 清空通话列表
} else {
    pollCallsAfterDelay();  // 稍后重试
}

这个流程展示了Android Telephony系统的高度分层设计,从底层Modem事件到上层应用通知,涉及多个进程间的通信和同步机制。

我已经为你详细总结了Android Telephony通话状态更新消息的完整上报流程。这个分析涵盖了从Modem底层到上层应用的9个关键阶段:

核心要点速览:

  1. Modem主动上报 - 发送RING/NO CARRIER等未经请求的回复
  2. Vendor RIL处理 - 解析AT指令,调用RIL_onUnsolicitedResponse
  3. RIL Framework - 获取Wakelock,通过HIDL转发消息
  4. HIDL Service - 将C++消息转换为Java AIDL调用
  5. Java Framework - VoiceIndication通知CallStateRegistrants
  6. CallTracker轮询 - 主动调用getCurrentCalls查询通话列表
  7. 解析通话列表 - 处理Modem返回的AT+CLCC响应
  8. 状态更新 - GsmCdmaCallTracker更新本地通话对象
  9. 应用感知 - 通过TelephonyCallback通知上层应用

关键的设计模式包括:

  • 主动轮询 - 上报只是信号,通话详情需轮询获取
  • Wakelock管理 - 防止CPU过度休眠
  • 分层通信 - Native/HAL/Java各层清晰解耦
  • 异步处理 - 所有操作都在事件循环中进行

如果你需要深入了解特定阶段(如AT指令的详细解析、HIDL消息转换、或特定通话操作的实现),我可以提供更详细的代码分析!