本流程图基于MTK平台 Android 7.0,普通电话,本流程只作为沟通学习使用
通过前面关于 MO 和 MT 的分析和学习,我们大致了解了整个Phone的两个主要流程,今天我们要了解的是整个流程中 Call 的状态是如何变化的。这里有参考到 4.4 的状态分析,有些区别。
DriverCall.State
当 modem 发生状态改变时,它会通过 RILC 和 RILJ 将状态上报到我们 framework 层,接收并转换这些状态的正是我们的 DriverCall。
源码分析
不管是MO还是MT流程,我们都会执行 RIL.responseCallList 这里会调用 DriverCall.stateFromCLCC 方法,如下:
//这里将 modem 传上来的状态进行转换
public static State stateFromCLCC(int state) throws ATParseEx {
switch(state) {
case 0: return State.ACTIVE;
case 1: return State.HOLDING;
case 2: return State.DIALING;
case 3: return State.ALERTING;
case 4: return State.INCOMING;
case 5: return State.WAITING;
default:
throw new ATParseEx("illegal call state " + state);
}
}
modem状态分析
通过底层反馈的log信息如下:
//AT 指令
Line 13952: 01-23 15:04:54.777 I/AT ( 868): AT> AT+CLCC (RIL_CMD_READER_2, tid:512639079504)
Line 13959: 01-23 15:04:54.784 I/AT ( 868): AT< +CLCC: 1,0,3,0,0,"10010",129 (RIL_CMD_READER_2, tid:512639079504)
通过 AT< +CLCC 我们看到modem给我们反馈的信息,其代表的含义如下:
2.7.6 AT+CLCC 列举当前的电话 该命令返回当前电话的列表 命令格式AT+CLCC 响 应 OK 如果当前没有电话 +CLCC: <id1>, <dir>, <stat>, <mode>, <mpty> [ ,<number>, <type> [ <alpha> ] ]
<idx> 整数类型电话识别码
<dir> 0 移动台发起MO的电话 1 移动台终止MT的电话
<stat> 电话的状态 0 正在迚行 1 保持 2 拨号MO 3 振铃MO 4 来电MT 5 等待MT
<mode> 0语音 1数据 2传真 3语音(语音跟随数据) 4语音(语音数据交换) 5语音(语音传真交换) 6数据(语音跟随数据) 7数据(数据语音交换) 8传真(语音传真交换) 9 未知
<mpty> 0 电话不是多方会话中的成员 1 电话是多方会话中的成员
<number> 字符类型的电话号码格式通过<type>指定
<type> 129没有国际接入码“+” 145有国际接入码“+”
<alpha> <number>在电话本中的数字表示
我们 DriverCall.State 转换的规则就是根据上面 “stat” 字段的描述来将 modem 的状态转换到 framework 层的状态。
GsmCdmaCall.call
然后会调用到 GsmCdmaCallTracker.handlePollCalls 方法,这里会通过判断是 MO 还是 MT 来得到 call 的三种类型:
//GsmCdmaCallTracker.handlePollCalls
// Connection appeared in CLCC response that we don't know about
if (mPendingMO != null && mPendingMO.compareTo(dc)) {//MO的时候
.....省略部分代码
mPendingMO.mIndex = i;
mPendingMO.update(dc);//通过 DriverCall 的状态来确定 GsmCdmaCall 的类型
mPendingMO = null;
.....省略部分代码
} else { //MT的时候
if (Phone.DEBUG_PHONE) {
log("pendingMo=" + mPendingMO + ", dc=" + dc);
}
/// M: CC: Remove handling for MO/MT conflict, not hangup MT @{
if (mPendingMO != null && !mPendingMO.compareTo(dc)) {
log("MO/MT conflict! MO should be hangup by MD");
}
/// @}
mConnections[i] = new GsmCdmaConnection(mPhone, dc, this, i);//通过创建连接来确定当前 GsmCdmaCall 的类型
//GsmCdmaConnection.parentFromDCState 获得 GsmCdmaCall 类型的具体方法
private GsmCdmaCall parentFromDCState (DriverCall.State state) {
switch (state) {
case ACTIVE:
case DIALING:
case ALERTING:
return mOwner.mForegroundCall;
//break;
case HOLDING:
return mOwner.mBackgroundCall;
//break;
case INCOMING:
case WAITING:
return mOwner.mRingingCall;
//break;
default:
throw new RuntimeException("illegal call state: " + state);
}
}
.....省略部分代码
Call.State(opt/telephony)
在对 GsmCdmaCall 进行分类后就会对 call 的状态进行分类,这里的 call 是指 frameworks-opt-telephony 下面的 call, 不管是 update 还是创建 connection 后都会执行 mParent.attach(this, dc);
//GsmCdmaCall.attach
public void attach(Connection conn, DriverCall dc) {
mConnections.add(conn);
mState = stateFromDCState (dc.state); //获得call当前的状态,根据DriverCall 的状态
}
//frameworks/opt/telephone Call.stateFromDCState 具体分类实现
public static State stateFromDCState (DriverCall.State dcState) {
switch (dcState) {
case ACTIVE: return State.ACTIVE;
case HOLDING: return State.HOLDING;
case DIALING: return State.DIALING;
case ALERTING: return State.ALERTING;
case INCOMING: return State.INCOMING;
case WAITING: return State.WAITING;
default: throw new RuntimeException ("illegal call state:" + dcState);
}
}
小结:
通过上面的分类,我们就成功的将 modem 传上来的状态进行了分类,分成了不同的 call 类型和 call 状态,它们的关系如下图
向三方暴露call的状态
PhoneConstants状态
在 GsmCdmaCallTracker.handlePollCalls 的后半部分会执行 updatePhoneState 方法,这个方法会决定 PhoneConstants 的状态,它的状态是根据上面的 Call.State(opt/telephony) 状态来决定的。
//GsmCdmaCallTracker.updatePhoneState
private void updatePhoneState() {
PhoneConstants.State oldState = mState;
if (mRingingCall.isRinging()) {
mState = PhoneConstants.State.RINGING; //设置状态
} else if (mPendingMO != null ||
!(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
mState = PhoneConstants.State.OFFHOOK; //设置状态
} else {
Phone imsPhone = mPhone.getImsPhone();
/// M: ALPS02192901. @{
// If the call is disconnected after CIREPH=1, before +CLCC, the original state is
// idle and new state is still idle, so callEndCleanupHandOverCallIfAny isn't called.
// Related CR: ALPS02015368, ALPS02161020, ALPS02192901.
// if ( mState == PhoneConstants.State.OFFHOOK && (imsPhone != null)){
if (imsPhone != null) {
/// @}
imsPhone.callEndCleanupHandOverCallIfAny();
}
mState = PhoneConstants.State.IDLE;//设置状态
}
.....省略部分代码
}
//frameworks/opt/telephone Call.java
public boolean isRinging() {
return this == INCOMING || this == WAITING;
}
public boolean isAlive() {
return !(this == IDLE || this == DISCONNECTED || this == DISCONNECTING);
}
public boolean isDialing() {
return this == DIALING || this == ALERTING;
}
}
TelephonyManager 中 call 的状态
上面装换成 PhoneConstants 状态后会执行 mPhone.notifyPhoneStateChanged(); 方法,通过父类层层调用,最总会调用到 DefaultPhoneNotifier.notifyPhoneState 方法,最后调用 convertCallState 方法将状态转换成 TelephonyManager 中call 的状态,如下代码:
/**
* Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
* constants for the public API.
*/
public static int convertCallState(PhoneConstants.State state) {
switch (state) {
case RINGING:
return TelephonyManager.CALL_STATE_RINGING;
case OFFHOOK:
return TelephonyManager.CALL_STATE_OFFHOOK;
default:
return TelephonyManager.CALL_STATE_IDLE;
}
}
当三方应用通过调用 getCallState 的时候就是返回的 TelephonyManager 的CALL_STATE_RINGING、CALL_STATE_OFFHOOK、CALL_STATE_IDLE 这三种状态:
//三方app 调用方式
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
tm.getCallState();
//源代码 frameworks/base/telephony TelephonyManager.java
/**
* Returns one of the following constants that represents the current state of all
* phone calls.
*
* {@link TelephonyManager#CALL_STATE_RINGING}
* {@link TelephonyManager#CALL_STATE_OFFHOOK}
* {@link TelephonyManager#CALL_STATE_IDLE}
*/
public int getCallState() {
try {
ITelecomService telecom = getTelecomService();
if (telecom != null) {
return telecom.getCallState();
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getCallState", e);
}
return CALL_STATE_IDLE;
}
小结:
到此处我们就将 framework 层 call 的状态暴露给了三方应用,它们的对应关系如下图:
内部call不同层次的对应关系
Connection.State(base/telecomm)
当创建 connection 的时候会执行 TelephonyConnection.updateStateInternal 方法,来设置connection 的状态,它的大部分状态也是根据 Call.State(opt/telephony) 的状态来设置的。
void updateStateInternal() {
......省略部分代码
switch (newState) {
case IDLE:
break;
case ACTIVE:
/// M: CC: ECC Retry @{
// Assume only one ECC exists
if (mTreatAsEmergencyCall
&& TelephonyConnectionServiceUtil.getInstance().isEccRetryOn()) {
Log.d(this, "ECC Retry : clear ECC param");
TelephonyConnectionServiceUtil.getInstance().clearEccRetryParams();
}
/// @}
setActiveInternal();
break;
case HOLDING:
setOnHold();
break;
case DIALING:
case ALERTING:
setDialing();
break;
case INCOMING:
case WAITING:
setRinging();
break;
case DISCONNECTED:
/// M: CC: ECC Retry @{
// Assume only one ECC exists
if (mTreatAsEmergencyCall
&& TelephonyConnectionServiceUtil.getInstance().isEccRetryOn()) {
Log.d(this, "ECC Retry : clear ECC param");
TelephonyConnectionServiceUtil.getInstance().clearEccRetryParams();
}
/// @}
setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
mOriginalConnection.getDisconnectCause(),
mOriginalConnection.getVendorDisconnectCause()));
close();
break;
case DISCONNECTING:
/// M: CC: ECC Retry @{
mIsLocallyDisconnecting = true;
/// @}
break;
}
}
}
通过上面的方法就可以根据 Call.State(opt/telephony) 设置 Connection 的大部分状态,包括:STATE_RINGING、STATE_DIALING、STATE_ACTIVE、STATE_HOLDING、STATE_DISCONNECTED,其余状态的转换条件如下:
STATE_INITIALIZING : 连接正在初始化状态,创建一个连接的时候会设置这个状态,它是连接的第一个状态
STATE_NEW :是一个新的连接,但是还没连接上,一般判断为创建一个紧急号码的connection的时候会设置成这个状态
STATE_RINGING : 一个来电连接,此时手机处于ringing状态,震动并响铃
STATE_DIALING : 一个处于外拨的连接,此时对方还没有应答,可以听到嘟嘟的声音
STATE_ACTIVE :一个连接处于活动状态,双方可以正常主动通信
STATE_HOLDING : 一个连接存于hold状态
STATE_DISCONNECTED : 一个断开连接,这个是连接的最终状态,
STATE_PULLING_CALL :表示一个连接正处于从远端连接拉到本地连接的一个状态(比如有两个设备但是共用一个号码)
CallState(services/Telecomm)
当 connection 创建完成后就会创建与之相对应的 call,这里 call 的状态是通过 packages/services/Telecom 下面的 call.getStateFromConnectionState 方法得到的,它是根据前面得到的 connection 状态来设置的。
//设置call(services/Telecomm)的状态
static int getStateFromConnectionState(int state) {
switch (state) {
case Connection.STATE_INITIALIZING:
return CallState.CONNECTING;
case Connection.STATE_ACTIVE:
return CallState.ACTIVE;
case Connection.STATE_DIALING:
return CallState.DIALING;
case Connection.STATE_DISCONNECTED:
return CallState.DISCONNECTED;
case Connection.STATE_HOLDING:
return CallState.ON_HOLD;
case Connection.STATE_NEW:
return CallState.NEW;
case Connection.STATE_RINGING:
return CallState.RINGING;
}
return CallState.DISCONNECTED;
}
相关状态说明:
NEW : 表面当前的call是新的并且没有被连接上,在telecom这个是call的默认初始状态
CONNECTING : 外拨call的初始状态成功后会转换成DIALING状态,失败后会转换成DISCONNECTED状态
SELECT_PHONE_ACCOUNT : 如果有多个phoneaccount会在外拨时出现这个状态,询问用户选择哪个账户拨打电话
DIALING :表明给一个call正处于dialing状态,如果对方接收会转换成ACTIVE状态,取消或者拒接会转换成DISCONNECTED
RINGING : call处于来电状态,接听转换成ACTIVE 否则转成DISCONNECTED
ACTIVE : call 以及接通,双方可以正常通话
ON_HOLD :call没有终止,但是不能相互通信,一般有ACTIVE转换得来
DISCONNECTED :当前的call已经断开连接
ABORTED : 这个call尝试创建连接,但在成功之前被主动取消掉了
DISCONNECTING :当前call正在断开连接,断开后会转入DISCONNECTED
Call.State(base/telecomm)
当call发生了一些改变后,我们会将 Call.State(base/telecomm) 的状态封装一下并转换成 Call.State(base/telecomm) 的状态,以备提供内 dialer 应用使用,相应的转换代码如下:
//ParcelableCallUtils.getParcelableState
private static int getParcelableState(Call call) {
int state = CallState.NEW;
switch (call.getState()) {
case CallState.ABORTED:
case CallState.DISCONNECTED:
state = android.telecom.Call.STATE_DISCONNECTED;
break;
case CallState.ACTIVE:
state = android.telecom.Call.STATE_ACTIVE;
break;
case CallState.CONNECTING:
state = android.telecom.Call.STATE_CONNECTING;
break;
case CallState.DIALING:
state = android.telecom.Call.STATE_DIALING;
break;
case CallState.DISCONNECTING:
state = android.telecom.Call.STATE_DISCONNECTING;
break;
case CallState.NEW:
state = android.telecom.Call.STATE_NEW;
break;
case CallState.ON_HOLD:
state = android.telecom.Call.STATE_HOLDING;
break;
case CallState.RINGING:
state = android.telecom.Call.STATE_RINGING;
break;
case CallState.SELECT_PHONE_ACCOUNT:
state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
break;
}
// If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
// Unless we're disconnect*ED*, in which case leave it at that.
if (call.isLocallyDisconnecting() &&
(state != android.telecom.Call.STATE_DISCONNECTED)) {
state = android.telecom.Call.STATE_DISCONNECTING;
}
return state;
}
相关状态说明
STATE_NEW : 新创建的call的状态
STATE_DIALING : call当前正在外拨但是还没有连接上
STATE_RINGING : call当前正处于来电状态,但是没有连接上
STATE_HOLDING : call当前处于hold状态
STATE_ACTIVE : call处于活动状态支持互相通话
STATE_DISCONNECTED : 当前的call释放了所有资源处于断开连接状态
STATE_SELECT_PHONE_ACCOUNT : 等待用户选择phoneaccount
STATE_PRE_DIAL_WAIT :也是等待用户选择phoneaccount
STATE_CONNECTING : 外拨的初始状态,如果成功会转换成STATE_DIALING 否则转换成STATE_DISCONNECTED
STATE_DISCONNECTING : 正处于断开连接状态
STATE_PULLING_CALL : 表示一个连接正处于从远端连接拉到本地连接的一个状态(比如有两个设备但是共用一个号码)
Cal.State(Dialer/incallUI)
在incallUI中我们想分辨不同 call 的状态,就需要将上面 telecom 中的Call.State(base/telecomm) 转换成 dialer 中的 Cal.State(Dialer/incallUI),相关的转换代码如下:
private static int translateState(int state) {
switch (state) {
case android.telecom.Call.STATE_NEW:
case android.telecom.Call.STATE_CONNECTING:
return Call.State.CONNECTING;
case android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT:
return Call.State.SELECT_PHONE_ACCOUNT;
case android.telecom.Call.STATE_DIALING:
return Call.State.DIALING;
case android.telecom.Call.STATE_RINGING:
return Call.State.INCOMING;
case android.telecom.Call.STATE_ACTIVE:
return Call.State.ACTIVE;
case android.telecom.Call.STATE_HOLDING:
return Call.State.ONHOLD;
case android.telecom.Call.STATE_DISCONNECTED:
return Call.State.DISCONNECTED;
case android.telecom.Call.STATE_DISCONNECTING:
return Call.State.DISCONNECTING;
default:
return Call.State.INVALID;
}
}
相关状态说明
INVALID : call 初始化mState变量时会用到,没什么具体的含义
NEW : call 是新建的
IDLE : call处于空闲状态
CALL_WAITING : 来电,但是当前有个正处于activity状态的call
REDIALING : 拨号失败后再次尝试拨号
CONFERENCED : 会议电话中的一个call
CONNECTING : 外拨电话,dialer等待Telecom 的广播call状态发生改变完成
BLOCKED :当前的call在黑名单列表中
WAIT_ACCOUNT_RESPONSE : call状态指明现在正等待账户相应,在选择phoneaccount界面时候的中间状态
小结
通过上面的转换,我们就一步一步将modem下面反馈上来的 call 状态通过 DriverCall–telephony–telecom–dialer/incallUI ,从而和我们上层界面的 call 状态一一对应,他们的对应关系如下图:
总结
- 当 SystemServer 起来的时候就会通过 ActivityThread 去创建 PhoneApp,PhoneApp会调用PhoneGlobals 通过 PhoneFactory.makeDefaultPhones(this);创建相关的 phone 出来,在创建phone的时候会通过 int numPhones =TelephonyManager.getDefault().getPhoneCount();去得到创建几个 phone 和 RIL,最终是通过去读取 static final String PROPERTY_MULTI_SIM_CONFIG =”persist.radio.multisim.config”; 这个系统属性的值来判断的。这个属性值就包括DSDS(Dual SIMDual Standby 双卡双待单通 :两个radio但是同一时间只能存在一个,它们会快速的来回切换)、DSDA(DSDA - Dual SIM DualActive双卡双待双通:两个radio并且可以同时存在并使用)、TSTS(TSTS - Triple SIM Triple Standby三卡三待)等
- 创建完 phone 之后就会去创建三种类型的 call,包括:mForegroundCall、mBackgroundCall和 mRingingCall
- 对 call 分完类之后就会去创建 connection,协议规定一个 call 最多包含5个 connection(会议通话一次最多5个connection),mBackgroundCall 和 mRingingCall 都只包含一个connection
- connection 创建完成之后就会对 call 的状态进行分类包装并传递