主要內容
1. Call涉及的目錄結構及框架結構
2. InCallUI層的基本架構(所涉及的Presenter、Fragment及Activity)
3. Call的幾種狀態(對應phone狀態)及上報流程
4. GSM與IMS MO流程的差異
5. 分析問題的常用log
希望你在看完本篇以後能夠:
1.快速找到Call界面某個小時內容對應的fragment及presenter
2.結合log快速定位當前call的狀態
PS:內容均為博主個人經驗總結,如有出錯的地方歡迎指正。
1. Call涉及的目錄結構及框架結構
1.1 目錄結構
packages/
apps/Dialer/
packages/
apps/InCallUI
packages/
services/Telecomm
packages/
services/Telephony
framework/base/telecomm
framework/opt/telephony
(vendor/…/
imsIms Call)
Dialer
撥打電話的入口,來電不會經過Dialer。但是撥打電話的出口不光是Dialer,在聯系人和短信裡也有撥打電話的出口。代碼運行在dialer進程。
InCallUI
負責顯示通話界面的信息,來電信息。dialer進程。
Telecomm
處理Intent,發送廣播,設置call的狀態,audio狀態。system_process和telecomm:ui進程。
Telephony
向下層傳遞撥號,注冊了很多廣播,申請很多權限(service data sms wap network)。 phone進程
telecomm
提供placeCall的接口(自android M開始),創建outgoingCall的connection,通知上層成功建立connection
telephony
撥號 也就是dial命令的下發,但是如果是Ims網絡就會有下面一步
Vendor/ims
創建ImsConnection,ImsCall,撥號。phone進程。
1.2 框架結構
這只是框架上的一個大致結構
在實際的流程中並不一定是自上而下或者自下而上的,也有可能跳過某個模塊直接傳遞信息。
比如在Dialer撥號的時候,就是直接調用framework/base/telecmm中TelecomManager的placeCall接口撥打電話。
2. InCallUI層的基本架構(所涉及的Presenter、Fragment及Activity)
2.1InCallUI內有多個對應的Presenter和Fragment
Presenter和Fragment的關系:Fragment直接控制界面上的控件,並處理一些簡單的邏輯,對應的Presenter處理稍復雜的邏輯。
CallCardFragment
CallCarfPresenter
CallButtonFragment
CallButtonPresenter
CallCard和CallButton是綁定在一起顯示的,有CallCard就一定會有CallButton
CallCard顯示撥打的電話的信息,包括聯系人姓名,號碼,通話時長,通話類型等(稍後有展示),CallButton包括可以對當前通話進行的操作,如通話保持,通話靜音,通話錄音等。
VideoCallFragment
VideoCallPresenter
主要是視頻內容,在進入退出視頻模式時同時處理camera、audio相關信息。
AnswerFragment
AnswerPresenter
顯示新來電,或者視頻升級請求,重點在於對不同類型的通話顯示不同的選項(也可根據運營商進行不同配置)。
DialpadFragment
DialpadPresenter
撥號盤,添加通話的時候顯示。一般沒什麼問題,之前有該顯不顯的問題,同時dialpad的顯示會影響endCallButton的大小,兩者是同步變化的。
ConferenceManagerFragment
ConferenceMangerPresenter
沒遇到過這裡的問題,只是准備PPT的時候才看到這個。
InCallActivity啟動的時候,在顯示完動畫後,調用showCallCardFragment()顯示CallCardFragment(CallCardFragment本身也有動畫)然後Fragment內new Presenter。
其他幾個也有各自的顯示流程,不一定是在InCallActivity的動畫顯示後立刻show出來。
另:
VideoCallFragment不是通過showFragment()顯示的。
2.2 通話界面布局分析
注:界面被輕量定制,並非和原生完全一樣。
圖1 CallCardFragment和CallButtonFragment
圖中除了priamry_card_info.xml和call_button_fragment.xml是布局文件,其他標注均為控件id。
AnswerFragment和DialpadFragment共用一個FramLayout
@+id/answer_and_dialpad_container
在顯示的時候覆蓋在CallCard的上層,比如CallCard中的photo(聯系人頭像)就是被dialpad給覆蓋。
圖2 VideoCallFragment(真人出境啦)
左側為普通視頻電話,右側為視頻會議電話。
注意,雖然右側看起來像是有4個小窗口顯示內容,但實際上和左邊一樣只有兩個控件顯示視頻,只不過右側上方的
視頻內容被分成了3塊。我們把黑色的背景色調成藍色,就容易區分出是兩個控件了。
至於為什麼兩張圖incomingVideo的位置不一樣,CallCard一個顯示一個沒顯示,是因為,點擊屏幕(或者等5s)後CallCard會隱藏,然後incomingVideo會自動調整位置到中央。
圖3 AnswerFragment
更多的界面就不一一詳細說明了。
如果你看到了不常見的控件,那它在代碼中的位置,和臨近位置的控件的代碼位置也是臨近的(有點拗口= =)。
2.3 InCallPresenter和InCallActivity
這個為什麼要單獨列成一條呢?因為這個Presenter比前面幾個Presenter都重要,它連結這界面的各個部分。
InCallPresenter
從CallList中獲取更新然後通知到InCallActivity,由InCallActivity控制界面顯示。
同時各個Fragment和Presenter可以獲得InCallPresenter的實例,進而更新/獲得數據。 單獨的Presenter負責對應的界面的邏輯,InCallPresenter負責整個界面的顯示,協調各個Fragment/Presenter之間的工作。
以視頻電話全屏為例,在VideoCallPresenter中有隱藏CallCard進入全屏模式的需求,在VideoCallPresenter中會通過InCallPresenter調用CallCardPresenter的onFullscreenModeChanged()方法,進而調用CallCardFragment的方法隱藏CallCard
InCallActivity
InCallActivity是InCallUI的主體,可以說所有的類都是圍繞著或者是為了這個Activity工作的,各個Fragment附著在InCallActivity上,InCallPresenter通過InCallActivity提供的get***Fragmment的方法,獲得具體Fragment對象, 更新界面。
Fragment是通過InCallActivity創建和顯示的,但是各個Presenter和InCallPresenter之間的交互
不一定全都經過InCallActivity(比如可以通過listener調用)
圖4 InCallUI基本架構
我們舉兩個例子 來說明界面上更新走過的流程:
1.設置CallStateLabel上的文本,比如"requesting video",代碼的執行經過InCallPresenter> CallCardPresenter> CallCardFragment,跳過了InCallActivity;
2.彈出dialog,比如“網絡不可用” ,代碼執行經過IncallPresenter > InCallActivity
還有另外一個比較重要的沒有體現在圖上的類叫做
TelecomAdapter。
它的職責主要是把界面上操作的命令向下層傳遞,具體點就是把對call的操作傳給Telecomm,包括接聽,掛斷,拒接,靜音,切換audioRoute等。
3. Call的幾種狀態(對應phone狀態)及上報流程
3.1 Call的狀態
下面這張圖不用記,知道有這麼多就行了。
不同模塊的call的狀態不一樣,有些能對應上,有些是獨有的對應不上的。
比較重要的是InCallUI裡的
Call.State和InCallPresenter的內部類
InCallState
InCallPresenter控制著界面顯示,這裡的InCallState就是界面顯示的依據。
TelephonyManager向外提供訪問手機狀態的接口,只有三種狀態。
圖5 Call的狀態
下圖表現的是InCallUI Call狀態的轉化以及對應的TelephonyManager.CALL_STATE_***狀態
圖6 InCallUI 中的Call狀態轉化
其中CONNECTING/SELECT_PHONE_ACCOUNT這兩個是
二者取其一。如果是雙卡手機並設置每次詢問,則撥號後的狀態為SELECT_PHONE_ACCOUNT,如果有是單卡或者默認某一張卡撥打,則狀態為CONNECTING,並且這個狀態很短暫,很快就轉換為DIALING。
3.2 上報流程
都是以UNSOL_RESPONSE_CALL_STATE_CHANGED消息上報為開始,
//CS
- 09-09 10:52:42.093 D/RILJ ( 4017): [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED [SUB1]//PS07-18 15:31:58.120
//PS
- D/ImsSenderRxr( 4499): [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED [id=1,DIALING,toa=129,norm,mo,0,voc,noevp,,cli=1,,3Call Details = 3 2 callSubState 0 videoPauseState2 mediaId0 Local Ability Peer Ability Cause code 0,CallFailCause Code= 501,CallFailCause String= null, ECT mask: 0] [SUB0]
流程中多次用到RegistrantList消息處理機制,比較關鍵的一個地方是TelephonyConnection裡,在外撥(或者來電)設置連接的時候注冊了一個消息
getPhone().registerForPreciseCallStateChanged(mHandler,MSG_PRECISE_CALL_STATE_CHANGED, null);當收到這個消息的時候會調用updateState()更新狀態。
updateState
在Telephony更新call的狀態的時候不同的狀態對應不同的方法,如setActiveInternal()
圖7 ims call state update(簡圖)
上圖也印證了之前說的,雖然層次上packages疊在framework上面,但實際上他們並不是嚴格按照相鄰的順序去調用。有可能隔著一層就調用了,也可能反復調用。
下圖以IMS電話接通狀態上報為例
圖7 ims call state update(詳圖)
4. GSM與IMS MO流程的差異
- 最大的區別是 IMS通話最後是通過高通私有代碼ims.app中撥出去的。
- IMS MO也要經過GSMPhone,判斷到IMS網絡可用以後會調用到ImsPhone中的dial()方法。
- ImsSenderRxr.java相當於CS call中的RIL.java,DIAL的命令也是在此發送
07-18 15:35:04.977 D/ImsSenderRxr( 4499): [0247]> DIAL[SUB0]
- ImsPhone中的log是在radio log中打印,但是ImsSenderRxr的log是在main log打印。
- 在ImsPhone的後續步驟中,會創建ImsPhoneConnection和ImsCall。
對比圖
圖8 GSM MO和IMS MO 對比
箭頭指向為對應步驟,可以很明顯的看到Ims的MO流程在這部分更復雜一些。針對IMS網絡下的通話,專門建立了ImsPhoneConnection ImsCall。
5. 分析問題的常用log
5.1InCallUI
前面說過InCallUI的界面顯示是以InCallState的狀態為根據的,而查看InCallState的關鍵字是Phone switching state:
- 14:42:29.721 I/InCall (24960): InCallPresenter - Phone switching state: NO_CALLS -> NO_CALLS
- 14:42:29.864 I/InCall (24960): InCallPresenter - Phone switching state: OUTGOING -> OUTGOING
- 14:42:30.262 I/InCall (24960): InCallPresenter - Phone switching state: OUTGOING -> PENDING_OUTGOING
- 14:42:31.194 I/InCall (24960): InCallPresenter - Phone switching state: PENDING_OUTGOING -> OUTGOING
- 14:42:34.975 I/InCall (24960): InCallPresenter - Phone switching state: OUTGOING -> INCALL
- 14:42:35.630 I/InCall (24960): InCallPresenter - Phone switching state: INCALL -> INCALL
InCallUI會根據這個狀態顯示界面信息。
這個狀態一般是跟CallList裡的call同步,但也有例外,就是會
撤回狀態,關鍵字Undo the state change
Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState);
14:42:35.998 I/InCall (24960): InCallPresenter – Undo the state change: INCOMING ->NO_CALLS
這時候就出現了界面顯示和實際情況不一樣,上面的log就對應來電沒有界面。
注意:INCALL包括DISCONNECTING和DISCONNECTED,雖然這兩個是短暫的狀態,但是看到的最後幾個INCALL狀態可能call已經斷開了。
CallList中的onUpdate(),這是常見的看InCallUI中call的數量和狀態的地方,關鍵字CallList - onUpdate
- 17:13:21.514 I/InCall ( 4166): CallList - onUpdate - [Call_0, CONNECTING, [Capabilities:
- 17:13:21.568 I/InCall ( 4166): CallList - onUpdate - [Call_0, DIALING, [Capabilities: CAP
- 17:13:29.997 I/InCall ( 4166): CallList - onUpdate - [Call_0, ACTIVE, [Capabilities: CAPA
- 17:13:30.255 I/InCall ( 4166): CallList - onUpdate - [Call_0, ACTIVE, [Capabilities: CAPA
- 17:13:39.896 I/InCall ( 4166): CallList - onUpdate - [Call_1, CONNECTING, [Capabilities:]
- 17:13:43.179 I/InCall ( 4166): CallList - onUpdate - [Call_1, CONNECTING, [Capabilities:
- 17:13:43.266 I/InCall ( 4166): CallList - onUpdate - [Call_1, DIALING, [Capabilities: CA
- 17:13:43.451 I/InCall ( 4166): CallList - onUpdate - [Call_1, DIALING, [Capabilities: CA
- 17:13:43.471 I/InCall ( 4166): CallList - onUpdate - [Call_0, ONHOLD, [Capabilities: CAP
- 17:13:43.492 I/InCall ( 4166): CallList - onUpdate - [Call_0, ONHOLD, [Capabilities: CAP
5.2 Telecom
另外自android 6.0開始,Telecomm加了一個關鍵字為Event的log,會打印在Telecom內執行的關鍵步驟的log。打印出來如下
- 17:22:05.385 I/Telecom ( 769): Event: Call 8: CREATED, null
- 17:22:05.394 I/Telecom ( 769): Event: Call 8: SET_CONNECTING, ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService},
- 17:22:05.449 I/Telecom ( 769): Event: Call 8: AUDIO_ROUTE, EARPIECE
- 17:22:09.161 I/Telecom ( 769): Event: Call 8: BIND_CS, ComponentInfo{com.android.phone…
- 17:22:09.240 I/Telecom ( 769): Event: Call 8: CS_BOUND, ComponentInfo{com.android.phone…
- 17:22:09.241 I/Telecom ( 769): Event: Call 8: START_CONNECTION, tel:*****
- 17:22:09.399 I/Telecom ( 769): Event: Call 8: SET_DIALING, successful outgoing call
- 17:22:17.395 I/Telecom ( 769): Event: Call 8: SET_ACTIVE, active set explicitly
- 17:23:03.636 I/Telecom ( 769): Event: Call 8: REQUEST_DISCONNECT, null
- 17:23:04.211 I/Telecom ( 769): Event: Call 8: SET_DISCONNECTED, disconnected set explicitly> DisconnectCause [ Code: (LOCAL) Label: () Description: () Reason: (LOCAL) Tone: (27) ]
- 17:23:05.612 I/Telecom ( 769): Event: Call 8: DESTROYED, null
DisconnectCause是常用的查看通話斷開原因的log。
注意:Telecom的log是在system_process進程打印,用adb命令抓log的時候要加上-b system
5.3 MT 來電
在main log中我常看
- 00:01:21.154 D/Telephony( 1389): PstnIncomingCallNotifier: handleNewRingingConnection
如果這條看不到還可以查CallsManager中的setCallState,可以從狀態轉化中看到來電狀態。
- 14:42:34.888 I/Telecom ( 2920): CallsManager: setCallState DIALING -> ACTIVE, call: [100747014, DIALING,
5.4 MO DIAL
IMS(PS)
- 19849: 07-05 18:30:35.325 D/ImsSenderRxr( 3325): [0056]> DIAL[SUB0]
- 20027: 07-05 18:30:35.365 D/ImsSenderRxr( 3325): [0056]< DIAL [SUB0
有DIAL回傳才表示撥號成功
GSM(CS)
- 2409: 09-08 17:29:56.206 D/RILJ ( 4017): [6301]> DIAL [SUB1]
- 2442: 09-08 17:29:56.250 D/RILJ ( 4017): [6301]< DIAL [SUB1]
5.5 HANGUP
- 07-18 15:32:37.899 D/ImsSenderRxr( 4499): [0240]> HANGUP[SUB0]
- 07-18 15:32:37.995 D/ImsSenderRxr( 4499): [0240]< HANGUP [SUB0]
5.6 IMS
在IMS網絡下通話的很多log是在main log中打印的,其中幾個比較關鍵的類包括ImsSenderRxr、ImsServiceSub,以這兩個類名為關鍵字在log中搜索就好了,或者以關鍵字/ImsSe把這兩個一起搜出來
- 18:30:37.522 D/ImsSenderRxr( 3325): [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED [id=1,DIALING,toa=129,norm,mo,0,voc,noevp,,cli=1,,3Call Details = 3 2 callSubState 0 videoPauseState2 mediaId5 Local Ability Peer Ability Cause code 0,CallFailCause Code= 0,CallFailCause String= null, ECT mask: 0] [SUB0]
- 18:30:37.522 D/ImsServiceSub( 3325): Message received: what = 1
- 18:30:37.522 D/ImsServiceSub( 3325): >handleCalls
- 18:30:37.523 D/ImsServiceSub( 3325): handleCalls: dc = id=1,DIALING,toa=129,norm,mo,0,voc,noevp,,cli=1,,3Call Details = 3 2 callSubState 0 videoPauseState2 mediaId5 Local Ability Peer Ability Cause code 0,CallFailCause Code= 0,CallFailCause String= null, ECT mask: 0
希望閱讀完對您有所幫助,也歡迎給出意見和建議。
//CS
- 09-09 10:52:42.093 D/RILJ ( 4017): [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED [SUB1]//PS07-18 15:31:58.120
//PS
- D/ImsSenderRxr( 4499): [UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED [id=1,DIALING,toa=129,norm,mo,0,voc,noevp,,cli=1,,3Call Details = 3 2 callSubState 0 videoPauseState2 mediaId0 Local Ability Peer Ability Cause code 0,CallFailCause Code= 501,CallFailCause String= null, ECT mask: 0] [SUB0]