我来分析一下这个仓库中的来电和去电流程。让我查看相关代码:
现在让我查看来电处理和通话状态轮询的逻辑:
好的,现在我已经收集了足够的信息。让我整理一下来电和去电的完整流程:
Android Telephony 来电去电流程详解 ☎️📞
基于 frameworks-opt-telephony 代码分析,电话呼叫处理涉及多个层次的复杂交互。
核心架构
应用层 (Telecom/Dialer)
↓
Phone/GsmCdmaPhone
↓
CallTracker (GsmCdmaCallTracker / ImsPhoneCallTracker)
↓
Call (GsmCdmaCall / ImsCall)
↓
Connection (GsmCdmaConnection / ImsConnection)
↓
RIL → Modem
一、去电流程(Mobile Originated Call - MO) 📤
流程架构图
用户拨号
↓
Dialer App → Telecom Framework
↓
Phone.dial()
↓
GsmCdmaCallTracker.dialGsm() / dialCdma()
↓
创建 GsmCdmaConnection (mPendingMO)
↓
RIL.dial() → Modem
↓
轮询通话状态 (pollCallsWhenSafe)
↓
呼叫建立 (ALERTING → ACTIVE)
详细步骤
步骤 1: 用户发起拨号
应用通过 Telecom 框架发起呼叫:
TelecomManager.placeCall(Uri.parse("tel:10086"), ...)
步骤 2: Phone.dial() 处理拨号请求
GsmCdmaPhone.dial() 或 ImsPhone.dial() 根据网络类型选择:
- CS 域(电路域):传统 2G/3G 语音
- IMS 域(VoLTE/VoWiFi):4G/5G 语音
步骤 3: CallTracker 创建连接
public synchronized Connection dialGsm(String dialString, DialArgs dialArgs)
throws CallStateException {
int clirMode = dialArgs.clirMode;
UUSInfo uusInfo = dialArgs.uusInfo;
Bundle intentExtras = dialArgs.intentExtras;
boolean isEmergencyCall = dialArgs.isEmergency;
if (isEmergencyCall) {
clirMode = CommandsInterface.CLIR_SUPPRESSION;
if (Phone.DEBUG_PHONE) log("dial gsm emergency call, set clirModIe=" + clirMode);
}
// note that this triggers call state changed notif
clearDisconnected();
// Check for issues which would preclude dialing and throw a CallStateException.
checkForDialIssues(isEmergencyCall);
String origNumber = dialString;
dialString = convertNumberIfNecessary(mPhone, dialString);
// The new call must be assigned to the foreground call.
// That call must be idle, so place anything that's
// there on hold
if (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE) {
// this will probably be done by the radio anyway
// but the dial might fail before this happens
// and we need to make sure the foreground call is clear
// for the newly dialed connection
switchWaitingOrHoldingAndActive();
// This is a hack to delay DIAL so that it is sent out to RIL only after
// EVENT_SWITCH_RESULT is received. We've seen failures when adding a new call to
// multi-way conference calls due to DIAL being sent out before SWITCH is processed
// TODO: setup duration metrics won't capture this
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// do nothing
}
// Fake local state so that
// a) foregroundCall is empty for the newly dialed connection
// b) hasNonHangupStateChanged remains false in the
// next poll, so that we don't clear a failed dialing call
fakeHoldForegroundBeforeDial();
}
if (mForegroundCall.getState() != GsmCdmaCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
}
mPendingMO = new GsmCdmaConnection(mPhone, dialString, this, mForegroundCall,
dialArgs);
if (intentExtras != null) {
Rlog.d(LOG_TAG, "dialGsm - emergency dialer: " + intentExtras.getBoolean(
TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL));
mPendingMO.setHasKnownUserIntentEmergency(intentExtras.getBoolean(
TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL));
}
mHangupPendingMO = false;
mMetrics.writeRilDial(mPhone.getPhoneId(), mPendingMO, clirMode, uusInfo);
mPhone.getVoiceCallSessionStats().onRilDial(mPendingMO);
if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
|| mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
// Phone number is invalid
mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
关键操作:
- clearDisconnected(): 清理已断开的连接
- checkForDialIssues(): 检查是否允许拨号
- 检查是否有活动呼叫阻止新呼叫
- 检查飞行模式
- 检查服务状态
- switchWaitingOrHoldingAndActive(): 如果有活动通话,先置为保持
- 创建 mPendingMO: 待处理的移动始发连接
- 记录指标:
TelephonyMetrics.writeRilDial()
步骤 4: 发送拨号命令到 Modem
继续 dialGsm() 方法:
// 发送拨号命令到 RIL
mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(),
mPendingMO.getEmergencyNumberInfo(),
mPendingMO.hasKnownUserIntentEmergency(), clirMode, uusInfo,
obtainCompleteMessage());
通过 RIL 发送:
- GSM:
RIL_REQUEST_DIAL - CDMA:
RIL_REQUEST_CDMA_DIAL - IMS: 通过 IMS 栈处理
步骤 5: 通话状态轮询
Modem 开始建立呼叫后,会主动上报状态变化:
RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
GsmCdmaCallTracker 收到通知后触发轮询:
EVENT_CALL_STATE_CHANGE → pollCallsWhenSafe()
pollCallsWhenSafe() 流程:
pollCallsWhenSafe()
↓
mCi.getCurrentCalls(EVENT_POLL_CALLS_RESULT)
↓
RIL_REQUEST_GET_CURRENT_CALLS
↓
handlePollCalls(AsyncResult ar)
步骤 6: 处理轮询结果
if (conn == null && dc != null) {
// Connection appeared in CLCC response that we don't know about
if (mPendingMO != null && mPendingMO.compareTo(dc)) {
if (DBG_POLL) log("poll: pendingMO=" + mPendingMO);
// It's our pending mobile originating call
mConnections[i] = mPendingMO;
mPendingMO.mIndex = i;
mPendingMO.update(dc);
mPendingMO = null;
// Someone has already asked to hangup this call
if (mHangupPendingMO) {
mHangupPendingMO = false;
// Re-start Ecm timer when an uncompleted emergency call ends
if (!isPhoneTypeGsm() && mPhone.isEcmCanceledForEmergency()) {
mPhone.handleTimerInEmergencyCallbackMode(
GsmCdmaPhone.RESTART_ECM_TIMER);
}
try {
if (Phone.DEBUG_PHONE) log(
"poll: hangupPendingMO, hangup conn " + i);
hangup(mConnections[i]);
} catch (CallStateException ex) {
Rlog.e(LOG_TAG, "unexpected error on hangup");
}
// Do not continue processing this poll
// Wait for hangup and repoll
return;
}
} else {
if (Phone.DEBUG_PHONE) {
log("pendingMo=" + mPendingMO + ", dc=" + dc);
}
mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);
log("New connection is not mPendingMO. Creating new GsmCdmaConnection,"
+ " objId=" + System.identityHashCode(mConnections[i]));
Connection hoConnection = getHoConnection(dc);
if (hoConnection != null) {
log("Handover connection found.");
// Single Radio Voice Call Continuity (SRVCC) completed
mConnections[i].migrateFrom(hoConnection);
// Updating connect time for silent redial cases (ex: Calls are transferred
// from DIALING/ALERTING/INCOMING/WAITING to ACTIVE)
if (hoConnection.mPreHandoverState != GsmCdmaCall.State.ACTIVE &&
hoConnection.mPreHandoverState != GsmCdmaCall.State.HOLDING &&
dc.state == DriverCall.State.ACTIVE) {
mConnections[i].onConnectedInOrOut();
} else {
mConnections[i].onConnectedConnectionMigrated();
}
mHandoverConnections.remove(hoConnection);
if (isPhoneTypeGsm()) {
for (Iterator<Connection> it = mHandoverConnections.iterator();
it.hasNext(); ) {
Connection c = it.next();
Rlog.i(LOG_TAG, "HO Conn state is " + c.mPreHandoverState);
if (c.mPreHandoverState == mConnections[i].getState()) {
Rlog.i(LOG_TAG, "Removing HO conn "
+ hoConnection + c.mPreHandoverState);
it.remove();
}
}
}
mPhone.notifyHandoverStateChanged(mConnections[i]);
} else {
// find if the MT call is a new ring or unknown connection
log("New connection is not mPendingMO nor a pending handover.");
newRinging = checkMtFindNewRinging(dc,i);
if (newRinging == null) {
unknownConnectionAppeared = true;
if (isPhoneTypeGsm()) {
newUnknownConnectionsGsm.add(mConnections[i]);
} else {
newUnknownConnectionCdma = mConnections[i];
}
} else if (hangupWaitingCallSilently(i)) {
return;
}
}
}
hasNonHangupStateChanged = true;
}
处理逻辑:
- 将
mPendingMO转换为正式的mConnections[i] - 更新连接状态
- 处理 SRVCC(VoLTE → 2G/3G 切换)场景
步骤 7: 呼叫状态转换
去电状态机:
IDLE → DIALING → ALERTING → ACTIVE
public enum State {
@UnsupportedAppUsage IDLE,
ACTIVE,
@UnsupportedAppUsage HOLDING,
@UnsupportedAppUsage DIALING,
@UnsupportedAppUsage ALERTING,
@UnsupportedAppUsage INCOMING,
@UnsupportedAppUsage WAITING,
@UnsupportedAppUsage DISCONNECTED,
@UnsupportedAppUsage DISCONNECTING;
@UnsupportedAppUsage
public boolean isAlive() {
return !(this == IDLE || this == DISCONNECTED || this == DISCONNECTING);
}
@UnsupportedAppUsage
public boolean isRinging() {
return this == INCOMING || this == WAITING;
}
public boolean isDialing() {
return this == DIALING || this == ALERTING;
}
}
- DIALING: 拨号中(本地发起,网络未响应)
- ALERTING: 对方振铃中(网络已建立,等待接听)
- ACTIVE: 通话接通
步骤 8: 通知上层
Phone.notifyPreciseCallStateChanged()- 广播到 Telecom 框架
- 更新通话 UI
- 开始计费
二、来电流程(Mobile Terminated Call - MT) 📥
流程架构图
网络推送来电
↓
Modem → RIL_UNSOL_CALL_RING
↓
GsmCdmaCallTracker 收到通知
↓
pollCallsWhenSafe()
↓
handlePollCalls() 检测新来电
↓
创建 GsmCdmaConnection (mRingingCall)
↓
通知上层 (onNewRingingConnection)
↓
显示来电界面
↓
用户接听/拒接
详细步骤
步骤 1: Modem 上报来电
网络推送来电信号,Modem 通过 RIL 上报:
RIL_UNSOL_CALL_RING
RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED
对于 CDMA 还会收到:
RIL_UNSOL_CDMA_CALL_WAITING
步骤 2: CallTracker 注册监听
mCi = phone.mCi;
mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
收到事件后:
case EVENT_CALL_STATE_CHANGE:
pollCallsWhenSafe();
break;
步骤 3: 轮询获取通话列表
mCi.getCurrentCalls(obtainCompleteMessage(EVENT_POLL_CALLS_RESULT))
Modem 返回当前所有通话的 DriverCall 列表,包括:
- 通话索引
- 通话状态(INCOMING/WAITING/ACTIVE等)
- 号码信息
- 是否为来电(isMT = true)
步骤 4: 识别新来电
在 handlePollCalls() 中:
newRinging = checkMtFindNewRinging(dc, i);
checkMtFindNewRinging() 逻辑:
- 检查 DriverCall 的
isMT标志 - 检查状态是否为
INCOMING或WAITING - 如果是新的来电,创建新的 Connection 对象
步骤 5: 创建来电连接
mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);
将新连接添加到 mRingingCall:
if (mConnections[i].getCall() == mRingingCall) {
newRinging = mConnections[i];
}
步骤 6: 通知上层新来电
mPhone.notifyNewRingingConnection(newRinging);
通知链:
GsmCdmaCallTracker
↓
GsmCdmaPhone.notifyNewRingingConnectionP()
↓
CallManager.registerForNewRingingConnection()
↓
TelephonyConnectionService (Telecom 框架)
↓
InCallService (来电界面)
步骤 7: 显示来电界面
Telecom 框架接收到新来电通知后:
- 检查来电权限和来电显示设置
- 查询联系人数据库匹配号码
- 启动 InCallUI 显示来电界面
- 播放铃声/振动
步骤 8: 用户响应
接听电话:
public void acceptCall() throws CallStateException {
// FIXME if SWITCH fails, should retry with ANSWER
// in case the active/holding call disappeared and this
// is no longer call waiting
if (mRingingCall.getState() == GsmCdmaCall.State.INCOMING) {
Rlog.i("phone", "acceptCall: incoming...");
// Always unmute when answering a new call
setMute(false);
mPhone.getVoiceCallSessionStats().onRilAcceptCall(mRingingCall.getConnections());
mCi.acceptCall(obtainCompleteMessage());
} else if (mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
if (isPhoneTypeGsm()) {
setMute(false);
} else {
GsmCdmaConnection cwConn = (GsmCdmaConnection)(mRingingCall.getLatestConnection());
// Since there is no network response for supplimentary
// service for CDMA, we assume call waiting is answered.
// ringing Call state change to idle is in GsmCdmaCall.detach
// triggered by updateParent.
cwConn.updateParent(mRingingCall, mForegroundCall);
cwConn.onConnectedInOrOut();
updatePhoneState();
}
switchWaitingOrHoldingAndActive();
} else {
throw new CallStateException("phone not ringing");
}
}
处理逻辑:
- INCOMING: 直接接听 →
mCi.acceptCall() - WAITING: 呼叫等待场景 → 切换通话
switchWaitingOrHoldingAndActive()
发送到 RIL:
RIL_REQUEST_ANSWER (GSM)
RIL_REQUEST_CDMA_FLASH (CDMA)
拒接电话:
public void rejectCall() throws CallStateException {
// AT+CHLD=0 means "release held or UDUB"
// so if the phone isn't ringing, this could hang up held
if (mRingingCall.getState().isRinging()) {
mCi.rejectCall(obtainCompleteMessage());
} else {
throw new CallStateException("phone not ringing");
}
}
发送到 RIL:
RIL_REQUEST_HANGUP (挂断)
RIL_REQUEST_UDUB (User Determined User Busy)
步骤 9: 状态更新
接听后状态转换:
INCOMING/WAITING → ACTIVE
Modem 上报状态变化 → 再次轮询 → 更新连接状态
三、通话状态管理 📊
Call 对象分类
GsmCdmaCallTracker 维护三个 Call 对象:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public GsmCdmaCall mRingingCall = new GsmCdmaCall(this);
// A call that is ringing or (call) waiting
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public GsmCdmaCall mForegroundCall = new GsmCdmaCall(this);
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public GsmCdmaCall mBackgroundCall = new GsmCdmaCall(this);
- mRingingCall: 来电/呼叫等待
- mForegroundCall: 前台活动通话
- mBackgroundCall: 后台保持通话
连接数组
GsmCdmaConnection[] mConnections;
- GSM: 最多 19 个连接(7个CS + 12个IMS SRVCC)
- CDMA: 最多 8 个连接
状态轮询机制
触发轮询的事件:
EVENT_CALL_STATE_CHANGE: Modem 主动通知EVENT_RADIO_AVAILABLE: Radio 重新可用EVENT_REPOLL_AFTER_DELAY: 延迟重新轮询
轮询作用:
- 同步 Framework 和 Modem 的通话状态
- 检测新来电
- 检测通话断开
- 更新通话属性(时长、状态等)
四、关键功能
1. 通话保持与恢复
switchWaitingOrHoldingAndActive()
- GSM:
RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE - CDMA: 发送 Flash 命令
2. 多方通话(会议)
conference()
- 将保持通话和活动通话合并
- GSM:
RIL_REQUEST_CONFERENCE - CDMA: Flash 操作
3. 通话转移
explicitCallTransfer()
- 将通话转移给第三方
RIL_REQUEST_EXPLICIT_CALL_TRANSFER
4. 挂断通话
hangup(Connection conn)
hangupForegroundResumeBackground()
hangupWaitingOrBackground()
- 挂断指定连接
- 挂断前台,恢复后台
- 挂断等待或后台通话
五、IMS 通话处理
对于 VoLTE/VoWiFi 通话,使用 ImsPhoneCallTracker:
关键差异
- 不使用轮询: IMS 通过事件驱动
- ImsCall 对象: 通过 IMS 栈管理
- 更丰富的功能:
- 视频通话
- 通话升降级
- Wi-Fi 切换
- RTT(实时文本)
SRVCC 切换
Single Radio Voice Call Continuity:
- VoLTE → 2G/3G 无缝切换
ImsPhoneCallTracker→GsmCdmaCallTracker- 保持通话不中断
六、时序图总结
去电时序
Dialer App
↓ placeCall()
Telecom
↓
Phone.dial()
↓
CallTracker.dialGsm()
↓ 创建 mPendingMO
RIL.dial()
↓
Modem 拨号
↓
RIL_UNSOL_CALL_STATE_CHANGED
↓
pollCallsWhenSafe()
↓ getCurrentCalls()
Modem 返回通话列表
↓
handlePollCalls()
↓ 识别 mPendingMO → mConnections[i]
DIALING → ALERTING → ACTIVE
↓
notifyPreciseCallStateChanged()
↓
Telecom 更新 UI
来电时序
网络来电
↓
Modem 检测
↓
RIL_UNSOL_CALL_RING
↓
CallTracker.EVENT_CALL_STATE_CHANGE
↓
pollCallsWhenSafe()
↓ getCurrentCalls()
Modem 返回通话列表(包含新来电)
↓
handlePollCalls()
↓ checkMtFindNewRinging()
创建新 Connection → mRingingCall
↓
notifyNewRingingConnection()
↓
Telecom 收到通知
↓
启动 InCallUI
↓ 播放铃声
用户接听
↓
acceptCall()
↓ RIL.acceptCall()
Modem 接听
↓
RIL_UNSOL_CALL_STATE_CHANGED
↓
pollCallsWhenSafe()
↓
INCOMING → ACTIVE
↓
通话建立
七、关键日志标签
GsmCdmaCallTracker: CS 域通话追踪ImsPhoneCallTracker: IMS 域通话追踪GsmCdmaCall: 通话对象GsmCdmaConnection: 连接对象RIL: RIL 层命令和响应
这就是 Android Telephony 中完整的来电去电流程!整个系统通过轮询机制保持 Framework 和 Modem 状态同步,支持 GSM/CDMA/IMS 多种网络类型,并提供了丰富的通话管理功能。