1. 程式人生 > >Android Call分析(一) ---- Call物件詳解

Android Call分析(一) ---- Call物件詳解

Call(通話)相關的內容也是屬於Telephony模組,Call整體上可以分成兩類:
1. CS call,其中CS全稱是Circuit Switch,我們平常打電話走的就是CS的流程。
2. IMS PS call,其中PS全稱是Packet Switch,走IMS流程的Call有4類,分別是VoLTE(voice over LTE),ViLTE(video over LTE),VoWiFi(voice over wifi),ViWiFi(video over wifi)。

在分析具體的MT(Mobile Termination Call 被叫)/MO(Mobile Origination Call 主叫)流程之前,需要了解更多Call相關的基礎知識,日後才可以更好地理解各種通話的流程。

而本文要講解的就是Call.java物件。在Android原始碼中一共有5個“Call.java”檔案,所以理清它們之間的聯絡以及學會如何分辨不同的Call物件,這也是分析通話流程的前提之一。
我們需要重點關注的有4個:
1. /packages/apps/Dialer/InCallUI/src/com/android/incallui/Call.java
2. /packages/services/Telecomm/src/com/android/server/telecom/Call.java
3. /frameworks/base/telecomm/java/android/telecom/Call.java
4. /frameworks/opt/telephony/src/java/com/android/internal/telephony/Call.java
整個通話流程可以細分成Dialer(InCallUI),Telecomm Framework,Telecomm Service,Telephony Service,Telephony Framework這5個模組,除了Telephony Service,其他4個模組都有一個自己的Call.java,可想而知,整個通話的過程都會伴隨著對Call物件的處理。

http://blog.csdn.net/linyongan

一、Dialer(InCallUI)中的Call

在Android N中,InCallUI已經被放置到Dialer目錄之下,也許是為了更好地提醒開發者,要編譯InCallUI的話,直接編譯Dialer就行了。
對於InCallUI Call,
/packages/apps/Dialer/InCallUI/src/com/android/incallui/Call.java

public class Call {

它只是一個普通的類,沒有父類也沒有實現介面。
從它的構造方法來看,InCallUI Call是直接依賴Telecom Call(Telecom Framework中的Call)的

    public Call(android.telecom.Call telecomCall) {
        mTelecomCall = telecomCall;
        mId = ID_PREFIX + Integer.toString(sIdCounter++);
        //依據Telecom Call來更新自己的資訊
        updateFromTelecomCall();
        mTelecomCall.registerCallback(mTelecomCallCallback);
        mTimeAddedMs = System.currentTimeMillis();
    }

InCallUI Call中的State、Phone Account、ChildNumber等資訊都來源於Telecom Call。
InCallUI Call被CallList管理著,CallList中有一個Map來儲存InCallUI Call

//String儲存的是Call ID
private final HashMap<String, Call> mCallById = new HashMap<>();

InCallUI Call是在CallList的onCallAdded()方法被呼叫的時候建立的

    public void onCallAdded(final android.telecom.Call telecomCall) {
        final Call call = new Call(telecomCall);
        Log.d(this, "onCallAdded: callState=" + call.getState());

        if (call.getState() == Call.State.INCOMING ||
                call.getState() == Call.State.CALL_WAITING) {
            onIncoming(call, call.getCannedSmsResponses());
        } else {
            onUpdate(call);
        }
    }

CallList還依據Call的狀態,將InCallUI Call進行分類,對外提供不同型別的Call物件

所以通過CallList獲取到的Call物件都是InCallUI Call。
總結:在Dialer(InCallUI)模組中會見到兩種Call物件,基本都是InCallUI Call;另外一種Call就是Telecom Call(Telecom Framework中的Call),Telecom Call會很明顯地寫成android.telecom.Call,所以也是很好地分辨的。

二、Telecom Framework中的Call

Telecom Call,
/frameworks/base/telecomm/java/android/telecom/Call.java

public final class Call {

它是一個被定義成final型別的類,它是在Phone.java (framework\base\telecomm\java\android\telecom) 的internalAddCall()方法中被建立的

    private final List<Call> mCalls = new CopyOnWriteArrayList<>();

    final void internalAddCall(ParcelableCall parcelableCall) {
        //從ParcelableCall中取出資訊用於new Telecom Call
        Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
                parcelableCall.getState());
        mCallByTelecomCallId.put(parcelableCall.getId(), call);  
        //把Telecom Call加入List集合中
        mCalls.add(call);
        checkCallTree(parcelableCall);
        call.internalUpdate(parcelableCall, mCallByTelecomCallId);
        fireCallAdded(call);
     }

而ParcelableCall是一箇中間者的角色,在InCallController.java中先將Telecom Service中的Call轉換成ParcelableCall

ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall
    (call,videoProviderChanged /* includeVideoProvider */,
    mCallsManager.getPhoneAccountRegistrar());

然後再把ParcelableCall轉換成Telecom Call,這樣子就實現了Telecom Service Call向Telecom Call的轉變。

三、Telecom Service中的Call

Telecom Service中的Call,
/packages/services/Telecomm/src/com/android/server/telecom/Call.java

public class Call implements CreateConnectionResponse {

它實現了CreateConnectionResponse介面,說明它也負責Connection建立之後的一些事情的處理,比如成功建立Connection之後就需要通知UI介面重新整理以及狀態的更新。

Telecom Service Call是通話流程中最重要的Call物件,它擁有管理一通電話的能力(answer,reject,hold,disconnect等等),它由CallsManager建立和管理。
在通話過程中,CallsManager是這樣管理Call的:
這裡寫圖片描述
不管是MO/MT Call,都是在建立Telecom Service Call之後呼叫它自身的startCreateConnection()方法來發起建立Connection的動作,以及成功建立Connection之後通知UI重新整理介面的處理。

Telecom Service Call每執行一項重要的動作都會輸出相應的標誌性log

    public void answer(int videoState) {
        Preconditions.checkNotNull(mConnectionService);

        if (isRinging("answer")) {
            mConnectionService.answer(this, videoState);
            //列印log
            Log.event(this, Log.Events.REQUEST_ACCEPT);
        }
    }

log如下,以”Event: Call “為關鍵字:
14:16:35.776 I/Telecom (24667): Event: Call 8: REQUEST_ACCEPT, null

Telecom Service Call的狀態資訊源自Telephony Framework Call的State,大致流程是這樣的:
1. 先由Connection獲取到Telephony Framework Call的State,

    @Connection.java
    public Call.State getState() {
        Call c;

        c = getCall();

        if (c == null) {
            return Call.State.IDLE;
        } else {
            return c.getState();
        }
    }

2.接著將Connection State轉換成CallState

    @Call.java(/packages/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;
    }

3.最後由CallsManager呼叫setCallState()方法把Call的狀態set到對應的Telecom Service Call中。

總結:這4個Call的狀態轉換過程就是這樣子的:
Telephony Framework Call—>Telecom Service Call—>Telecom Call—> InCallUI Call.

四、Telephony Framework中的Call

Telephony Framework中的Call,
/frameworks/opt/telephony/src/java/com/android/internal/telephony/Call.java

public abstract class Call {

它是一個抽象類,它的繼承關係如下:

我們需要關注的是GsmCdmaCall和ImsPhoneCall。
GsmCdmaPhone,GsmCdmaCallTracker,GsmCdmaConnection,GsmCdmaCall四者關係緊密,如下圖:
這裡寫圖片描述
在GsmCdmaPhone初始化的時候會建立GsmCdmaCallTracker,GsmCdmaCallTracker負責管理GsmCdmaConnection和GsmCdmaCall,GsmCdmaCallTracker的內部有一個GsmCdmaConnection的陣列:

private GsmCdmaConnection mConnections[];

並且有常量控制著mConnections陣列陣列的大小,一個GsmCdmaConnection代表著一通電話,說明GSM最大允許同時存在19通,CDMA最大同時存在8通。

    public static final int MAX_CONNECTIONS_GSM = 19;   //7 allowed in GSM + 12 from IMS for SRVCC
    private static final int MAX_CONNECTIONS_PER_CALL_GSM = 5; //only 5 connections allowed per call
    private static final int MAX_CONNECTIONS_CDMA = 8;
    private static final int MAX_CONNECTIONS_PER_CALL_CDMA = 1; //only 1 connection allowed per call

同時,GsmCdmaCallTracker的內部也會建立三個GsmCdmaCall(GsmCdmaCall僅僅會在GsmCdmaCallTracker中被建立,建立之後不會再被重新賦值):

    public GsmCdmaCall mRingingCall = new GsmCdmaCall(this);
    // A call that is ringing or (call) waiting
    public GsmCdmaCall mForegroundCall = new GsmCdmaCall(this);
    public GsmCdmaCall mBackgroundCall = new GsmCdmaCall(this);

Telephony Framework Call的狀態有9種:

    public enum State {
        IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED, DISCONNECTING;
    }

問題1:那麼mRingingCall,mForegroundCall,mBackgroundCall分別對應Call的什麼狀態呢?

由於Telephony Framework Call的”ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING”這六種狀態跟DriverCall.State是一一對應的

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

在GsmCdmaConnection中有依據DriverCall.State將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);
        }
    }

所以mRingingCall,mForegroundCall,mBackgroundCall與GsmCdmaCall.mState的關係如下:

GsmCdmaCall GsmCdmaCall.mState
mRingingCall INCOMING,WAITING
mForegroundCall ACTIVE,DIALING,ALERTING
mBackgroundCall HOLDING

GsmCdmaCallTracker在初始化的時候就註冊監聽了Call狀態變化的訊息,

    public GsmCdmaCallTracker (GsmCdmaPhone phone) {
        mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
    }

所以當modem中Call狀態發生變化後,便會通知到GsmCdmaCallTracker,GsmCdmaCallTracker通過呼叫RILJ的getCurrentCalls()方法發起查詢modem當前的Call狀態列表,modem返回來的結果是DriverCall 集合。
再由GsmCdmaCallTracker的handlePollCalls()方法來對比自身mConnections集合與DriverCall 集合的差異,進而依據DriverCall的資訊跟新這對應GsmCdmaCall(mRingingCall,mForegroundCall,mBackgroundCall)的狀態,同時將當前GsmCdmaConnection與對應的GsmCdmaCall繫結。
整個過程時序圖如下:
這裡寫圖片描述

五、其他Call

ImsCall在/frameworks/opt/net/ims/src/java/com/android/ims/目錄下,與之前介紹的Call不一樣,ImsCall繼承自IMS call專屬的介面ICall,

public class ImsCall implements ICall {

ImsCall擁有處理IMS voice / video call的能力。ImsCall由ImsManager來建立,ImsManager是任意IMS actions的出發點。