本流程圖基於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 的狀態進行分類包裝並傳遞