Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程

本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉。

前置文章:

Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划

Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析

Android 4.4 Kitkat Phone工作流程浅析(三)__MO(去电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程简析

Android 4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程分析

概述

        前面五篇文章,将Android 4.4 的MO/MT流程衔接了起来,同时包括了UI结构简单分析,以及RILJ的工作流程分析。本篇以及后续关于Phone的文章,主要是对前面MO/MT主线的补充和说明,用以完善整个MO/MT流程。
        在整个MO/MT流程的分析过程中,遗漏了很多细枝末节,在弄清楚整个MO/MT的始末之后,便发现有一些疑问疑问,因此打算陆陆续续分析一些遗漏的细节。本篇文章的发起缘由很简单,就是想知道InCallActivity是如何显示和更新的,通过分析后发现,现在Android 4.4 的界面更新也是从Modem状态改变发起,并不像以前那样直接更新显示界面。
        从直观上来讲,当用户按下拨号键之后会立刻显示正在呼叫的界面,也就是InCallActivity。在Android 4.2中,这个界面叫做InCallScreen这一点前面的文章已经有提过,InCallScreen会通过上层调用显示出来。而在Android 4.4中,InCallActivity的显示则是由Call状态来决定的,Call的状态可以分为6种: ACTIVEHOLDINGDIALINGALERTINGINCOMINGWAITING,当发起MO流程并且对方还未接通这段时间,Call的状态是DIALING。
        这个DIALING状态是如何反应到界面上的呢?InCallActivity是如何知道当前Call的状态的呢?如果有看过MT流程的童鞋肯定知道,MT流程实际上就包含了Call状态的改变并从底层一路传递到上层,而DIALING的状态也是从底层反馈来的,因此整个流程和MT类似,分为三个部分:Telephony framework、TeleService、InCallUI,如下图:



Telephony Framework接收处理反馈

        通过前面的分析我们知道,所有的状态改变都是由底层Modem发起的,并将相关状态信息反馈到framework层的RILJ中。上层通过AT指令与Modem进行交互,关于AT的一些介绍信息可以看 这里这里以及 这里,本文主要介绍的是InCallActivity的显示更新,那为什么会提到AT指令呢?这得从源头说起。
        当用户发起MO流程之后,告诉Modem执行Dial操作,此时Modem的状态就会随之改变,并将状态改变信息通知到上层,因此这里会涉及到一些系统的radio log,可以看到发起拨号操作的相关重要log信息如下:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 01-01 18:11:47.039  1061  1061 D RILJ    :  RIL(1) :[0147]> DIAL  
  2. ... ...省略  
  3. 01-01 18:11:47.044   682   692 D use-Rlog/RLOG-AT: AT> ATD13800138000;  
  4. 01-01 18:11:47.044   682   696 I use-Rlog/RLOG-RIL: RIL_URC2_PROXY wakeup  
  5. 01-01 18:11:47.044   682   692 D use-Rlog/RLOG-AT: ATD13800138000;  
  6. ... ...省略   
  7. 01-01 18:11:47.047   682   707 D use-Rlog/RLOG-AT: OK  
  8. 01-01 18:11:47.047   682   707 D use-Rlog/RLOG-AT: AT< OK  
  9. 01-01 18:11:47.047   682   707 D use-Rlog/RLOG-AT: RIL_CMD_READER_2:OK  
  10. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT:   
  11. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: +CIEV: 5, 1  
  12. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT:   
  13. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: +ECPI: 1,130,0,0,0,0,"13800138000",129,""  
  14. 01-01 18:11:47.047   682   707 D use-Rlog/RLOG-AT: RIL_CMD_READER_2 Enter processLine  
  15. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: AT< +CIEV: 5, 1  
  16. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: RIL_URC_READER:+CIEV: 5, 1  
  17. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: RIL_URC_READER Enter processLine  
  18. 01-01 18:11:47.047   682   707 I use-Rlog/RLOG-AT: AT read start  
  19. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-RIL: Nw URC:+CIEV: 5, 1  
  20. 01-01 18:11:47.047   682   705 E use-Rlog/RLOG-RIL: Unhandled unsolicited result code: +CIEV: 5, 1  
  21. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: AT< +ECPI: 1,130,0,0,0,0,"13800138000",129,""  
  22. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: RIL_URC_READER:+ECPI: 1,130,0,0,0,0,"13800138000",129,""  
  23. 01-01 18:11:47.047   682   692 D use-Rlog/RLOG-AT: response received on RIL_CMD_READER_2, tid:3086469296  
  24. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: RIL_URC_READER Enter processLine  
  25. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-RIL: Nw URC:+ECPI: 1,130,0,0,0,0,"13800138000",129,""  
  26. ... ...省略  
  27. 01-01 18:11:47.048  1061  1274 D RILJ    :  RIL(1) :[0147]< DIAL   
  28. 01-01 18:11:47.048  1061  1274 V RILJ    :  RIL(1) :[UNSL RIL]< UNSOL_CALL_PROGRESS_INFO {1, 130, 0, 0, 0, 0, 13800138000, 129, }  
这里简单的分析一下重要的log信息:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. RIL(1) :[0147]> DIAL  
这表示发起DIAL请求,紧接着执行ATD即AT拨号指令:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 01-01 18:11:47.044   682   692 D use-Rlog/RLOG-AT: AT> ATD13800138000;  
我们在《 Android 4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程简析》文章中有讲过,可以根据serial号查看AT指令的配对,同时也提到了Log中的“>”和“<”所代表的含义,即“>”表示request,“<”表示response,上面两条log信息可以解释为RILJ发起了两次request请求。根据第一条AT指令的serial号"0147"我们可以在后面找到对应的response:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 01-01 18:11:47.048  1061  1274 D RILJ    :  RIL(1) :[0147]< DIAL   
这表明整个拨号的request和response已经完成,在此期间Modem主动返回了以下信息:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: AT< +ECPI: 1,130,0,0,0,0,"13800138000",129,""  
该条AT指令+ECPI是MTK添加的,在标准AT指令中查询不到,具体含义如下:
ECPI即Call Progress Information的缩写,用于开启/禁用Call Progress Information。
+ECPI:<call_id>,<msg_type>,<is_ibt>,<is_tch>,<dir>,<call_mode>,[<number>,<type>],[<disc_cause>]
对应的解释如下图:


图中黑色加粗部分为目前MTK Android 4.4有使用的msg_type,这一点可以在GSMCallTracker中的handleCallProcessInfo()方法的注释中找到。对应着这个表我们来梳理一下
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 01-01 18:11:47.047   682   705 D use-Rlog/RLOG-AT: AT< +ECPI: 1,130,0,0,0,0,"13800138000",129,""  
以上log重要信息如下:
call_id:1,call id 为1;
msg_type:130,表示CSMCC_CALL_ID_ASSIGN_MSG,也就是Dialing;
dir:0,CLCC_MO_CALL,表示MO操作;
call_mode:0,CLCC_VOICE_CALL,表示普通语音拨号;
number:138001380000,表示主叫号码;
type:129,National call,也就是本国电话,如果是145则表示国际电话;
在Modem完成DIAL操作之后,紧接着返回了以下log:
[plain]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 01-01 18:11:47.048  1061  1274 V RILJ    :  RIL(1) :[UNSL RIL]< UNSOL_CALL_PROGRESS_INFO {1, 130, 0, 0, 0, 0, 13800138000, 129, }  
这是一条UnSolicited response消息,处理方法是RIL.java中的 processUnsolicited(),类型为:UNSOL_PROGRESS_INFO。
        根据前文《 Android 4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程分析》可以知道,这里会开始在RILJ中执行ProcessUnSolicited()方法,并且对应类型为UNSOL_PROGRESS_INFO。RIL.java将处理之后的消息通过notifyRegistrants()的方式传递给GsmCallTracker,在GsmCallTracker的 handleCallProcessInfo()方法中可以看到是如何定义Call状态为DIALING的,代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //... ... 省略部分代码  
  2. if (msgType == 132 || msgType == 6)  
  3.     dc.state = DriverCall.State.ACTIVE;  
  4. else if (msgType == 131)  
  5.     dc.state = DriverCall.State.HOLDING;  
  6. else if (msgType == 130 && callId != 254)  
  7.     //从log中可以看到msgType=130,call_id=1  
  8.     dc.state = DriverCall.State.DIALING;  
  9. else if (msgType == 2)  
  10.     dc.state = DriverCall.State.ALERTING;  
  11. else if (msgType == 0)  
  12. {  
  13.     for (j = 0; j < MAX_CONNECTIONS; j++) {  
  14.         if (mConnections[j] != null) {  
  15.             count ++;  
  16.         }  
  17.     }  
  18.     if (mState == PhoneConstants.State.IDLE ||   
  19.     (count == 0 &&  mForegroundCall.getState() == GsmCall.State.DIALING))  
  20.     {  
  21.     /* if the 2nd condition is true, that means we make a MO call, receiving +ECPI: 130,  
  22.     * then receiving +ECPI: 133 immediately due to MT call (+ECPI: 0) is receiving*/  
  23.     if (count == 0 &&  mForegroundCall.getState() == GsmCall.State.DIALING)  
  24.         log("MO/MT conflict!!");  
  25.         dc.state = DriverCall.State.INCOMING;  
  26.     }  
  27.     else  
  28.         dc.state = DriverCall.State.WAITING;  
  29. }  
当状态改变之后便会通过GsmPhone的 notifyPreciseCallStateChanged()方法发起响应,如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if ((hasNonHangupStateChanged || newRinging != null) && crssAction != CrssAction.SWAP && !(hasPendingReplaceRequest && msgType == 133)) {  
  2.     log("notify precise call state changed");  
  3.     mPhone.notifyPreciseCallStateChanged();  
  4. }  
之后会通过观察者模式方式调用到CallManager的 handleMessage()方法中,case为 EVENT_PRECISE_CALL_STATE_CHANGED,代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. case EVENT_PRECISE_CALL_STATE_CHANGED:  
  2. //... ...省略部分代码  
  3.     index = (msg.what - EVENT_PRECISE_CALL_STATE_CHANGED) / NOTIFICATION_ID_OFFSET;  
  4.     mPreciseCallStateRegistrantsGemini[index].notifyRegistrants((AsyncResult) msg.obj);  
  5.     mPreciseCallStateRegistrants.notifyRegistrants((AsyncResult) msg.obj);  
  6.     handle3GSwitchLock();  
这里会有MTK的Gemini处理,即双卡处理,通过同样的观察者模式将状态改变信息通过notifyRegistrants()方法发送到TeleService中。整个流程如下图:


TeleService消息处理

        CallManager将Call状态改变的信息告诉TeleService中的 CallStateMonitor,通过这个名字可以很容易知道它是用来监视Call状态的。同时 CallNotifierCallModeler均注册了CallStateMonitor的状态改变回调,一旦Call状态改变便会通知CallNotifier和CallModeler。这里CallNotifier并没有做什么大的动作,只是更新了近距离感应器的状态,判断是否接通如果接通则震动这类,跟界面相关的调用则在CallModeler中。
        在 CallModeleronPhoneStateChanged()方法中,将信息传递到 CallHandlerServiceProxyonUpdate()方法中:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if (!ignoreUpdate()) {  
  2.     if (updatedCalls.size() > 0) {  
  3.         for (int i = 0; i < mListeners.size(); ++i) {  
  4.             mListeners.get(i).onUpdate(updatedCalls);  
  5.         }  
  6.     }  
  7. }  
这里会触发BluetoothManager、 CallHandlerServiceProxy、DTMFTonePlayer三个类中的 onUpdate()方法回调,这里我们查看CallHandlerServiceProxy中的onUpdate()方法即可。对于上面的代码再多说几句,原生Android 4.4中,CallModeler的onPhoneStateChanged方法并没有 ignoreUpdate()方法,这是MTK加入的主要目的是用于判断是否忽略本次界面更新,用于自动拒接和快速挂断正在响铃的电话两种场景。代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /* 
  2.  * The function to judge whether should skip update calls to InCallUI, 
  3.  * for auto reject case, or quick hang up ringing case. 
  4.  * When 1A + 1R, if ringing call is hanged up while query(ringtone), 
  5.  * CallNotifier will not notify InCallUI the onIncoming(), then we should ignore update calls to InCallUI; 
  6.  * or will show callcard with ringing call information but no AnswerFragment shown. 
  7.  */  
  8. private boolean ignoreUpdate() {  
  9.     boolean shouldIgnore = false;  
  10.     final boolean hasActiveFgCall = mCallManager.hasActiveFgCall();  
  11.     final boolean hasActiveBgCall = mCallManager.hasActiveBgCall();  
  12.     shouldIgnore = (hasActiveFgCall || hasActiveBgCall) && PhoneGlobals.getInstance().notifier.hasPendingCallerInfoQuery();  
  13.     Log.i(TAG, "ignoreUpdate()... shouldIgnore: " + shouldIgnore);  
  14.     return shouldIgnore;  
  15. }  
通过代码可以很清楚的知道,当CallerInfo没有查询完毕时 hasPendingCallerInfoQuery()返回true,则忽略本次界面更新。
在CallHandlerServiceProxy的onUpdate()方法中,首先会去执行bindService操作也就是与InCallUI建立联系,代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void onUpdate(List<Call> calls) {  
  3.     synchronized (mServiceAndQueueLock) {  
  4.         if (mCallHandlerServiceGuarded == null) {  
  5.             //设置更新类型为METHOD_UPDATE  
  6.             enqueueUpdate(calls);  
  7.             //与CallHandlerService建立连接  
  8.             setupServiceConnection();  
  9.             return;  
  10.         }  
  11.     }  
  12.     //执行更新  
  13.     processUpdate(calls);  
  14. }  
更新类型包括:
QueueParams.METHOD_INCOMING:更新类型为INCOMING;
QueueParams.METHOD_UPDATE:更新类型为UPDATE;
QueueParams.METHOD_DISCONNECT:更新类型为DISCONNECT;
QueueParams.METHOD_VT_DIAL_SUCCESS:更新类型为VT_DIAL_SUCCESS,视屏通话拨号成功;
QueueParams.METHOD_VT_SETTING_PARAMS:更新类型为VT_SETTING_PARAMS,视屏通话设置参数;
QueueParams.METHOD_VT_STATE_CHANGE:更新类型为VT_STATE_CHANGE,视屏通话状态改变;
QueueParams.METHOD_UPDATE_RECORD_STATE:更新类型为UPDATE_RECORD_STATE,通话录音;
Android原生只有前面三种类型,后面均由MTK添加。
        紧接着通过 setupServiceConnection()方法与InCallUI的CallHandlerService建立连接,这里需要注意在建立连接成功后会调用 onServiceConnected()方法,进一步调用 onCallHandlerServiceConnected()方法,并在该方法中继续调用 processQueue()方法,进而调用到最终跳转方法即 processUpdate()
这里大家可能会有疑问, onUpdate()方法中的 processUpdate()方法什么时候调用呢?在第一次执行Dial操作时,TeleService和InCallUI还没有建立联系,因此需要先bindService,等连接建立成功后,后续的更新则会直接调用 onUpdate()方法中的 processUpdate()方法,经过如此调用之后便会跳转到InCallUI中执行界面显示与更新。整个流程如下图:

InCallUI显示/更新

        在TeleService的 processUpdate()方法中,使用AIDL的方式调用 mCallHandlerServiceGuarded.onUpdate(),最终跳转到InCallUI中的 CallHandlerService.onUpdate()方法中,代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void onUpdate(List<Call> calls) {  
  3.     try {  
  4.         Log.i(TAG, "onUpdate: " + calls);  
  5.         //注意:这里的类型是ON_UPDATE_MULTI_CALL  
  6.         mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, calls));  
  7.     } catch (Exception e) {  
  8.         Log.e(TAG, "Error processing onUpdate() call.", e);  
  9.     }  
  10. }  
这里很奇怪的一点是,类型居然是ON_UPDATE_MULTI_CALL,而代码中也有ON_UPDATE_CALL类型,这是google原生的,MTK没有改过,不知道是何用意。
后面继续跳转到 CallListnotifyListenersOfChange()方法:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Sends a generic notification to all listeners that something has changed. 
  3.  * It is up to the listeners to call back to determine what changed. 
  4.  */  
  5. private void notifyListenersOfChange() {  
  6.     for (Listener listener : mListeners) {  
  7.         listener.onCallListChange(this);  
  8.     }  
  9. }  
这里会执行listener的回调,分别在 AnswerPresenterInCallPresenter中。CallList只是负责将状态改变通知到listener,是否处理则由listener们自己决定。AnswerPresenter只有在incoming的时候才会处理,因此这里应该查看 InCallPresenteronCallListChange()方法:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public void onCallListChange(CallList callList) {  
  3.     if (callList == null) {  
  4.         return;  
  5.     }  
  6.     InCallState newState = getPotentialStateFromCallList(callList);  
  7.     //这里去判断是否显示/关闭InCallActivity  
  8.     newState = startOrFinishUi(newState);  
  9. //... ...省略部分代码  
  10. }  
这里会跳转到 startOrFinishUi()中进行判断,因为是第一次启动 InCallActivity界面,最后会通过 startActivity()的方式启动InCallActivity,到此整个InCallActivity的界面显示流程就结束了。后续Modem侧状态改变则根据该流程传递到InCallPresenter,由InCallPresenter来响应不同的状态所需要启动/关闭的界面。整个流程如下图:


小结

        在Android 4.2中界面显示是在placeCall返回之后调用的,但到了Android 4.4中,因为UI和Logic的分离,界面的显示完全依赖于Logic返回的状态信息。关于InCallActivity的显示和更新流程总体上和MT流程是类似的即分为: Telephony frameworkTeleServiceInCallUI
        最后需要提一点,如果想主动显示或者更新InCallActivity界面,应该怎么办呢?在 CallModeler.java中有一个 updateCalls()public方法,可以通过构造CallModeler的对象来调用updateCalls()方法完成InCallActivity界面的显示和更新,代码如下:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public void updateCalls() {  
  2.     final List<Call> updatedCalls = Lists.newArrayList();  
  3.     doUpdate(true, updatedCalls);  
  4.     if (!ignoreUpdate()) {  
  5.         if (updatedCalls.size() > 0) {  
  6.             for (int i = 0; i < mListeners.size(); ++i) {  
  7.                 mListeners.get(i).onUpdate(updatedCalls);  
  8.             }  
  9.         }  
  10.     }  
  11. }  
        注意:该方法是MTK自己加入的,原生AOSP并没有,如果想使用也可以仿照这种方式添加。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值