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物件的處理。
一、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的出發點。