本流程图基于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 状态,它们的关系如下图

android 双卡判断那个卡槽有sim卡_数据

向三方暴露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 的状态暴露给了三方应用,它们的对应关系如下图:

android 双卡判断那个卡槽有sim卡_数据_02

内部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 状态一一对应,他们的对应关系如下图:

android 双卡判断那个卡槽有sim卡_Line_03

总结

  • 当 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 的状态进行分类包装并传递