Android N Call 狀態分析

本流程圖基於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);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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)
  • 1
  • 2
  • 3

通過 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>在電話本中的數字表示
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我們 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);
        }
    }

.....省略部分代碼
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

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);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

小結:

通過上面的分類,我們就成功的將 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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

當三方應用通過調用 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

小結:

到此處我們就將 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;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

通過上面的方法就可以根據 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 :表示一個連接正處於從遠端連接拉到本地連接的一個狀態(比如有兩個設備但是共用一個號碼)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

相關狀態說明:

 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

相關狀態說明

 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 : 表示一個連接正處於從遠端連接拉到本地連接的一個狀態(比如有兩個設備但是共用一個號碼)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

相關狀態說明

 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界面時候的中間狀態
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

小結

通過上面的轉換,我們就一步一步將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 的狀態進行分類包裝並傳遞
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章