好贷网好贷款

MTK的InCallScreen分析

发布时间:2016-12-3 8:25:47 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"MTK的InCallScreen分析",主要涉及到MTK的InCallScreen分析方面的内容,对于MTK的InCallScreen分析感兴趣的同学可以参考一下。

InCallScreen是什么 本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文以MTK平台Android 4.2为分析对象,MTK在原生的基础之上添加了许多自己的东西,因此个别地方跟原生代码以及QCOM的代码不同,请读者知悉。 在电话的呼出流程中,我们最后需要按下拨号键,才能将电话拨打出去,那么在按下拨号键之后,我们可以看到会弹出一个界面,显示拨号信息以及一些其他信息,这个界面就是我们的InCallScreen界面。当然,在来电的时候,弹出的界面依然是InCallScreen,在我们接通电话之后显示的那个界面仍然是InCallScreen。也就是说在通话过程中,我们一直可见并操作的那个界面就是InCallScreen。 图 1 InCallScreen界面 图1中所示就是InCallScreen不同情况下所展示的界面,分别是拨号、接通、来电三种情况下InCallScreen的现实情况。 InCallScreen结构分析 经过对比后可以发现,InCallScreen的拨号以及接通时,界面表现基本一致,而来电界面主要由于多了一个滑动接听的控件,从而导致界面不太一样。这里我们队InCallScreen的结构分析,采用接通之后的界面。如图2: 图 2 来电/去电接通后InCallScreen InCallScreen布局分析 在InCallScreen.java中,我们可以在onCreate方法中找到InCallScreen加载的布局文件,即incall_screen.xml。在incall_screen.java文件中,我们可以看到有以下几个控件: call_card:显示当前通话的信息,比如来电号码,通话时间,移动运营商等等; incall_touch_ui:包括挂断按钮,显示DTMF拨号盘按钮,扬声器,静音,暂停,加入通话等几个按钮,就是通话界面下方的控制按钮; otaCallCardStub:CDMA模式下跟OTA相关的控件; manageConferencePanelStub:多方通话管理界面; vtInCallScreenStub:视屏通话控件; dtmf_twelve_key_dialer_stub:这个控件为DTMF控件,也就是我们点击按钮之后,会弹出一个0~9以及*和#的拨号盘。 因为我们这里主要分析一般模式下的通话界面,因此暂不涉及到视屏通话。 总的来讲,在图2显示的界面中,我们直观能够看到的控件主要是:call_card以及incall_touch_ui这两块。当我们点击DTMF弹出按钮之后,会显示DTMF控件。当然在我们接入多方通话之后,就会看到多方通话的界面了。 call_card通话信息展示 call_card控件,实际上显示的信息主要为通话联系人的相关信息,布局如下: [html] view plaincopy <?xml version="1.0" encoding="utf-8"?>   <com.android.phone.CallCard xmlns:android="http://schemas.android.com/apk/res/android"       android:id="@+id/call_info_container"        android:layout_width="match_parent"       android:layout_height="match_parent"       android:orientation="vertical">          <include android:id="@+id/primary_call_info"            layout="@layout/primary_call_info" />          <RelativeLayout android:id="@+id/largeAreaForSharing"           android:layout_width="match_parent"           android:layout_height="match_parent"           android:visibility="gone"/>   </com.android.phone.CallCard>   整个布局情况如下图: 图 3  CallCard界面 详细信息请参看primary_call_info.xml。 incall_touch_ui通话控制界面 这里所谓的控制主要包括了在通话界面底部的一些操作按钮,比如:挂断、显示/取消显示DTMF拨号界面、开启/关闭扬声器、启动/停止静音、开启/取消来电保持、添加多路通话等一些操作按钮。当然这里并没有将所有的控制按钮或者控制控件全部显示出来,比如当来电时,我们需要滑动接听电话,这里的滑动控件即MultiWaveView也属于incall_touch_ui.xml布局。布局如图4: 图 4 InCallTouchUi InCallScreen初始化流程 通过查看Phone.apk的AndroidManifest.xml文件可以看到: [html] view plaincopy <activity android:name="InCallScreen"       android:theme="@style/Theme.InCallScreen"  <!-- InCallScreen的Theme  -->       android:label="@string/phoneIconLabel"       android:excludeFromRecents="true"    <!-- 该Activity不会显示在最近使用列表中 -->       android:launchMode="singleInstance"   <!-- 该Activity为单例模式 -->       android:screenOrientation="nosensor"   <!-- 该Activity不会横竖屏切换,默认竖屏 -->       android:configChanges="keyboardHidden"  <!-- 该Activity显示时隐藏keyboard -->       android:exported="false">                <!-- 该Activity不能被其它调用 -->   </activity>   [email protected]/Theme.InCallScreen可以看到: [html] view plaincopy <style name="Theme.InCallScreen" parent="@android:style/Theme.Holo.NoActionBar">       <item name="android:windowBackground">@android:color/black</item>       <item name="*android:windowAnimationStyle">@style/InCallAnimationStyle</item>   </style>   可以看到InCallScreen的Theme中没有ActionBar,窗口背景为黑色,有过场动画。 因为InCallScreen为单例模式,第一次启动时调用onCreate而后面则会调用其onNewIntent方法。我们知道在onCreate方法中,一般都是对一些对象进行创建并初始化,以及设置布局文件等等。在InCallScreen的onCreate方法中,完成了PhoneApp对象的获取,以及Window参数的设置等等,在这些过程中我们需要关注以下三个方法: initInCallScreen:初始化CallCard以及InCallTouchUi等截面; registerForPhoneStates:注册关于Phone状态改变的监听事件,这也就是为什么Phone状态改变之后InCallScreen能够收到变化消息的原因,这一点我们在来电流程中也有提及; internalResolveIntent:该方法用于处理InCallScreen收到的Intent信息; 同样,在onNewIntent方法中主要调用了internalResolveIntent方法,因此下面我们着重分析以下几个方法在InCallScreen初始化的过程中作用。 initInCallScreen 该方法在InCallScreen的onCreate方法中调用,仅执行一次,代码如下: [java] view plaincopy private void initInCallScreen() {       ... ...省略       // Initialize CallTime 通话时间初始化       mCallTime = new CallTime(this);       // Initialize the CallCard. 通话信息初始化       mCallCard = (CallCard) findViewById(R.id.callCard);       ... ...省略       //第二路通话界面初始化       mSecCallInfo = (ViewStub) findViewById(R.id.secondary_call_info);       mCallCard.setInCallScreenInstance(this);       //通话录音按钮初始化       mVoiceRecorderIcon = (ImageView) findViewById(R.id.voiceRecorderIcon);       mVoiceRecorderIcon.setBackgroundResource(R.drawable.voice_record);       mVoiceRecorderIcon.setVisibility(View.INVISIBLE);       // Initialize the onscreen UI elements. 通话控制布局初始化       initInCallTouchUi();       ... ...省略       // The DTMF Dialpad. DTMF拨号盘初始化       ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);       mDialer = new DTMFTwelveKeyDialer(this, stub);       mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);       // Initialize VTInCallScreen 视屏电话初始化       mVTInCallScreen = new VTInCallScreenProxy(this, mDialer);   }   可以看到这里全都是对InCallScreen上布局的一些初始化过程,我们继续看到其中对于InCallTouchUi初始化的代码: [java] view plaincopy private void initInCallTouchUi() {       ... ...省略       mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);       mInCallTouchUi.setInCallScreenInstance(this);       // 挂断并通话短信回复       mRespondViaSmsManager = new RespondViaSmsManager();       mRespondViaSmsManager.setInCallScreenInstance(this);   }   通过代码可以知道InCallScreen上的布局显示几乎都是在initInCallScreen方法中做的,如果我们修改了InCallScreen的布局那么我们应该在这里对修改后的布局进行初始化。 registerForPhoneStates 该方法在initInCallScreen方法之后,我们在来电流程中也有分析过该方法,代码如下: [java] view plaincopy private void registerForPhoneStates() {       if (!mRegisteredForPhoneStates) {           if (FeatureOption.MTK_GEMINI_SUPPORT) {               ... ...省略               mCMGemini.registerForIncomingRingGemini(mHandler, PHONE_INCOMING_RING, null, PhoneConstants.GEMINI_SIM_1);               mCMGemini.registerForIncomingRingGemini(mHandler, PHONE_INCOMING_RING2, null, PhoneConstants.GEMINI_SIM_2);               ... ...省略           } else {               ... ...省略               mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);               ... ...省略   }   这里所用的mHandler就是InCallScreen的mHandler,这里通过register****方法注册监听实际上为观察者模式的运用。 internalResolveIntent 该方法是InCallScreen用于处理Intent的方法,代码如下: [java] view plaincopy private void internalResolveIntent(Intent intent) {       ... ...省略       if (action.equals(intent.ACTION_MAIN)) {           //是否显示Dialpad           if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {               boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);               if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);                  mApp.inCallUiState.showDialpad = showDialpad;               final boolean hasActiveCall = mCM.hasActiveFgCall();               final boolean hasHoldingCall = mCM.hasActiveBgCall();               if (showDialpad && !hasActiveCall && hasHoldingCall) {                   PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());               }           }              ... ...省略           //强制开启扬声器           if (FeatureOption.MTK_TB_APP_CALL_FORCE_SPEAKER_ON) {             if (intent.hasExtra(EXTRA_FORCE_SPEAKER_ON)) {                 boolean forceSpeakerOn = intent.getBooleanExtra(EXTRA_FORCE_SPEAKER_ON, false);                 if (forceSpeakerOn)                 {                   Log.e("MTK_TB_APP_CALL_FORCE_SPEAKER_ON", "forceSpeakerOn is true");                   if (!PhoneGlobals.getInstance().isHeadsetPlugged()                            && !(mApp.isBluetoothHeadsetAudioOn())) {                     //Only force the speaker ON while not video call and speaker is not ON                     if (!intent.getBooleanExtra(Constants.EXTRA_IS_VIDEO_CALL, false)                         && !PhoneUtils.isSpeakerOn(mApp)) {                       Log.e("MTK_TB_APP_CALL_FORCE_SPEAKER_ON", "PhoneUtils.turnOnSpeaker");                       PhoneUtils.turnOnSpeaker(mApp, true, true, true);                     }                   }                 }             }           }                      //视屏通话处理           if (FeatureOption.MTK_VT3G324M_SUPPORT) {               if (getInVoiceAnswerVideoCall()) {                   setInVoiceAnswerVideoCall(false);               }               if (mCM.getState() == PhoneConstants.State.RINGING) {                   if (DBG) {                       log("call manager state is ringing");                   }                   // When VT call incoming, use voice call incoming call GUI                   mVTInCallScreen.setVTVisible(false);                   mVTInCallScreen.setVTScreenMode(Constants.VTScreenMode.VT_SCREEN_CLOSE);               } else if (intent.getBooleanExtra(Constants.EXTRA_IS_VIDEO_CALL, false)) {                   if (DBG) {                       log("vt extra is true");                   }                   // When dialing VT call, inflate VTInCallScreen                   mVTInCallScreen.initVTInCallScreen();                   // When dialed a VT call, but dialed failed, needs not init state for dialing                   if (CallStatusCode.SUCCESS == mApp.inCallUiState.getPendingCallStatusCode()) {                       mVTInCallScreen.initDialingSuccessVTState();                   }                   mVTInCallScreen.initDialingVTState();                   mVTInCallScreen.initCommonVTState();                   if (PhoneConstants.State.IDLE != PhoneGlobals.getInstance().mCM.getState() &&                               !VTCallUtils.isVideoCall(mCM.getActiveFgCall())) {                       // When voice is connected and place a VT call, need close VT GUI                       mVTInCallScreen.setVTScreenMode(Constants.VTScreenMode.VT_SCREEN_CLOSE);                   } else {                       mVTInCallScreen.setVTScreenMode(Constants.VTScreenMode.VT_SCREEN_OPEN);                   }               } else {                   // set VT open or close according the active foreground call                   if (mCM.getState() != PhoneConstants.State.IDLE && VTCallUtils.isVideoCall(mCM.getActiveFgCall())) {                       if (DBG) {                           log("receive ACTION_MAIN, but active foreground call is video call");                       }                       mVTInCallScreen.initVTInCallScreen();                       mVTInCallScreen.initCommonVTState();                       mVTInCallScreen.setVTScreenMode(Constants.VTScreenMode.VT_SCREEN_OPEN);                   } else if (!intent.getBooleanExtra(Constants.EXTRA_IS_NOTIFICATION, false)) {                       mVTInCallScreen.setVTScreenMode(Constants.VTScreenMode.VT_SCREEN_CLOSE);                   }               }                mVTInCallScreen.updateVTScreen(mVTInCallScreen.getVTScreenMode());           }           return;       }       //接听电话时触发       if (action.equals(Intent.ACTION_ANSWER)) {           internalAnswerCall();           mApp.setRestoreMuteOnInCallResume(false);           return;       }       //OTA相关处理       if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {           if (!TelephonyCapabilities.supportsOtasp(mPhone)) {               throw new IllegalStateException(                   "Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "                   + intent);           }              setInCallScreenMode(InCallScreenMode.OTA_NORMAL);           if ((mApp.cdmaOtaProvisionData != null)               && (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {               mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;               mApp.cdmaOtaScreenState.otaScreenState =                       CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;           }           return;       }          // 异常和未定义intent处理       if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {           throw new IllegalStateException(               "Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "               + intent);       } else if (action.equals(Intent.ACTION_CALL)                  || action.equals(Intent.ACTION_CALL_EMERGENCY)) {           throw new IllegalStateException("Unexpected CALL action received by InCallScreen: "                                           + intent);       } else if (action.equals(ACTION_UNDEFINED)) {           Log.wtf(LOG_TAG, "internalResolveIntent: got launched with ACTION_UNDEFINED");           return;       } else {           Log.wtf(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);           return;       }   }   InCallScreen初始化小结 经过以上几个关键步骤之后,InCallScreen的初始化就完成了,紧接着自然是进入onResume方法执行,这里就不展开去介绍onResume方法了,其主要就是为显示InCallScreen之前做一些准备工作和检查工作。 InCallScreenUI控制流程 通过前面的分析,InCallScreen的UI控制主要包括以下6个方面: incomingCallWidget:接通/挂断/短信回复时需要使用; dialpadButton:也就是显示或隐藏拨号盘(DTMF); audioButton:开启/关闭扬声器; muteButton:开启/关闭麦克风静音,开启之后对方无法听到你的声音; holdButton:开启/关闭呼叫保持; addButton:增加多路通话;也就是在通话的过程中可以暂停当前通话,拨打另一路通话并接通; 当我们点击addButton之后,会弹出拨号盘提示添加一路通话,添加通话之后整个InCallScreen界面将会变成如图5所示: 图 5 添加一路通话界面以及添加后界面 在改变之后的界面中,我们可以看到多了两个图标:和,前者表示在两路通话之间进行切换,后者表示开启视屏电话。 incomingCallWidget滑动控件 该滑动控件在来电的时候,会显示在InCallScreen界面上,默认情况下用户可以选择接听、挂断、挂断并短信回复三种模式,向右滑动为接听,向左滑动为挂断,向上滑动为挂断并选择快捷短信回复,如图6: 图 6 incomingCallWidget在来电时的显示 该控件只有在来电的时候才会显示出来,用户可以滑动中间的白色部分到图5中的三个方向:左、上、右,分别触发不同的效果。实际上该控件和解锁界面上的滑动解锁使用的是同一控件,使用滑动的方式触发不同的效果,接下来我们就来看看其控制流程吧。 控制流程 1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <com.android.internal.widget.multiwaveview.GlowPadView        android:id="@+id/incomingCallWidget"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="center|bottom"        android:layout_marginTop="20dip"        android:layout_marginBottom="-110dip"        android:background="@android:color/black"        android:visibility="gone"        android:gravity="top"           prvandroid:targetDrawables="@array/incoming_call_widget_3way_targets"        prvandroid:targetDescriptions="@array/incoming_call_widget_3way_target_descriptions"        prvandroid:directionDescriptions="@array/incoming_call_widget_3way_direction_descriptions"        prvandroid:handleDrawable="@drawable/ic_in_call_touch_handle"        prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"        prvandroid:outerRadius="@*android:dimen/glowpadview_target_placement_radius"        prvandroid:outerRingDrawable="@*android:drawable/ic_lockscreen_outerring"        prvandroid:snapMargin="@*android:dimen/glowpadview_snap_margin"        prvandroid:vibrationDuration="20"        prvandroid:feedbackCount="1"        prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"        prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"        />   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java (3)工作流程 首先初始化,代码如下: [java] view plaincopy private GlowPadView mIncomingCallWidget;   mIncomingCallWidget = (GlowPadView) findViewById(R.id.incomingCallWidget);   mIncomingCallWidget.setOnTriggerListener(this);   通过初始化代码我们可以看到其注册了一个TriggerListener并且就在本类中就有其实现,那么我继续找到其TriggerListener的实现,代码如下: [java] view plaincopy /**   * Handles "Answer" and "Reject" actions for an incoming call.   * We get this callback from the incoming call widget   * when the user triggers an action.   */   @Override   public void onTrigger(View view, int whichHandle) {       ... ...省略       mShowInCallControlsDuringHidingAnimation = false;       switch (whichHandle) {           //来电选择接听电话           case ANSWER_CALL_ID:               if (DBG) log("ANSWER_CALL_ID: answer!");               cancelIncomingPingTime();               mInCallScreen.handleOnscreenButtonClick(R.id.incomingCallAnswer);               mShowInCallControlsDuringHidingAnimation = true;               mLastIncomingCallActionTime = SystemClock.uptimeMillis();               break;           //来电选择挂断并通过短信回复           case SEND_SMS_ID:               if (DBG) log("SEND_SMS_ID!");               mInCallScreen.handleOnscreenButtonClick(R.id.incomingCallRespondViaSms);               break;           //来电选择拒接           case DECLINE_CALL_ID:               if (DBG) log("DECLINE_CALL_ID: reject!");               mInCallScreen.handleOnscreenButtonClick(R.id.incomingCallReject);               mLastIncomingCallActionTime = SystemClock.uptimeMillis();               break;           default:               Log.wtf(LOG_TAG, "onDialTrigger: unexpected whichHandle value: " + whichHandle);               break;       }       //隐藏滑动控件       hideIncomingCallWidget();       // Regardless of what action the user did, be sure to clear out       // the hint text we were displaying while the user was dragging.       mInCallScreen.updateIncomingCallWidgetHint(0, 0);   }   最终根据用户的不同选择,跳转到InCallScreen.java中的handleOnscreenButtonClick方法去执行具体代码,如下: [java] view plaincopy public void handleOnscreenButtonClick(int id) {      ... ...省略      switch (id) {           //来电正常接听          case R.id.incomingCallAnswer:              internalAnswerCall();              break;          //来电拒接          case R.id.incomingCallReject:              ... ...省略              hangupRingingCall();              break;          //来电拒接并短信回复          case R.id.incomingCallRespondViaSms:              internalRespondViaSms();              break;          ... ...省略      //更新InCallTouchUi      updateInCallTouchUi();   (4)时序图 时序图以来电接听为例,如图7: 图 7 来电接听时序图 dialpadButton显示/隐藏拨号盘 这里提到的dialpadButton实际上为InCallTouchUi布局上的:。 该控件的作用是点击之后显示或隐藏拨号盘,当电话接通之后如果用户点击该控件则会如图8所示: 图 8 DTMF拨号盘 当用户再次点击该控件时,拨号盘隐藏。 控制流程 (1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <ToggleButton android:id="@+id/dialpadButton"                style="@style/InCallCompoundButton"                  android:background="@drawable/btn_dialpad"                  android:contentDescription="@string/onscreenShowDialpadText"/>   Phone/res/layout/incall_screen.xml [html] view plaincopy <ViewStub android:id="@+id/dtmf_twelve_key_dialer_stub"             android:layout="@layout/dtmf_twelve_key_dialer_view"             android:layout_width="match_parent"             android:layout_height="match_parent"             android:layout_marginBottom="@dimen/dialpad_vertical_margin_dtmf"/>   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java Phone/src/com/android/phone/inCallScreen.java (3)工作流程 控件初始化代码如下: dialpadButton控件初始化: [java] view plaincopy private CompoundButton mDialpadButton;   mDialpadButton = (CompoundButton) mInCallControls.findViewById(R.id.dialpadButton);   mDialpadButton.setOnClickListener(this);   mDialpadButton.setOnLongClickListener(this);   这里的LongClick实际上是,在我们长按该控件时会弹出一个toast显示该控件的作用。 拨号盘控件初始化: [java] view plaincopy private DTMFTwelveKeyDialer mDialer;   ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);   mDialer = new DTMFTwelveKeyDialer(this, stub);   我们先找到mDialpadButton的onClick实现方法,如下: [java] view plaincopy @Override   public void onClick(View view) {       int id = view.getId();       ... ...省略       switch (id) {           ... ...省略           case R.id.dialpadButton:           ... ...省略               mInCallScreen.handleOnscreenButtonClick(id);               break;           ... ...省略       }   }   这里还是调用了InCallScreen中的handleOnscreenButtonClick方法,代码如下: [java] view plaincopy public void handleOnscreenButtonClick(int id) {      ... ...省略      switch (id) {          ... ...省略          case R.id.dialpadButton:              onOpenCloseDialpad();              break;          ... ...省略   这里最终调用onOpenCloseDialpad方法去实现打开或者关闭拨号盘,这里我们继续查看onOpenCloseDialpad方法,代码如下: [java] view plaincopy public void onOpenCloseDialpad() {       ... ...省略       //判断拨号盘是否已经打开,如果是则隐藏反之则显示       if (mDialer.isOpened()) {           closeDialpadInternal(true);       } else {           openDialpadInternal(true);       }       mApp.updateProximitySensorMode(mCM.getState());   }   //显示拨号盘   private void openDialpadInternal(boolean animate) {       mDialer.openDialer(animate);       mApp.inCallUiState.showDialpad = true;   }   //以藏拨号盘   private void closeDialpadInternal(boolean animate) {       mDialer.closeDialer(animate);       mApp.inCallUiState.showDialpad = false;   }   这里的拨号盘实际上是InCallScreen中一块独立的布局,也就是DTMFTwelveKeyDialer,当我们点击dialpadButton后,最终调用到openDialpadInternal或者closeDialpadInternal,通过mDialer去实现拨号盘的显示或隐藏,如mDialer.openDialer代码如下: [java] view plaincopy public void openDialer(boolean animate) {       ... ...省略          if (!isOpened()) {           // 这里animate=true           if (animate) {               AnimationUtils.Fade.show(mDialerView);           ... ...省略   }   查看AnimationUtils.Fade.show方法如下: [java] view plaincopy public static void show(final View view) {           ... ...省略           view.setVisibility(View.VISIBLE);           ... ...省略   }   最终使得mDialerView显示到界面上。 (4)时序图 图 9 接通后显示拨号盘时序图 audioButton开启/关闭扬声器 扬声器实际上就是手机的外放,在InCallTouchUi布局上显示为:。 该控件的作用是点击之后开启或关闭扬声器,也就是使得对方的通话声音能够通过外放增大。如果我们此时连接了蓝牙耳机,那么显示界面如图10所示: 图 10 接入蓝牙耳机后InCallTouchUi上audioButton改变 我可以选择三种不同的音频输出方式:Speaker即扬声器,Handset earpiece手机听筒,Bluetooth蓝牙耳机。 控制流程 (1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <ToggleButton android:id="@+id/audioButton"                style="@style/InCallCompoundButton"                android:background="@drawable/btn_compound_audio"                android:contentDescription="@string/onscreenAudioText"/>   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java (3)工作流程 控件初始化代码如下: audioButton控件初始化: [java] view plaincopy private CompoundButton mAudioButton;   mAudioButton = (CompoundButton) mInCallControls.findViewById(R.id.audioButton);   mAudioButton.setOnClickListener(this);   mAudioButton.setOnLongClickListener(this);   这里的LongClick实际上是,在我们长按该控件时会弹出一个toast显示该控件的作用。找到audioButton的onClick实现方法,如下: [java] view plaincopy @Override   public void onClick(View view) {       int id = view.getId();       ... ...省略       switch (id) {           ... ...省略           case R.id.audioButton:           ... ...省略               handleAudioButtonClick();               break;           ... ...省略       }   }   这里的handleAudioButtonClick方法对是否接入了蓝牙耳机进行了判断,如果是则会像图9所示那样,弹出三个选项按钮。这里我们假设没有连接蓝牙耳机,代码如下: [java] view plaincopy private void handleAudioButtonClick() {       ... ...省略       //如果连接了蓝牙耳机则执行if里面的代码       if (inCallControlState.bluetoothEnabled) {           ... ...省略       } else {           ... ...省略           mInCallScreen.toggleSpeaker();       }   }   这里我们继续查看InCallScreen中的toggleSpeaker方法: [java] view plaincopy public void toggleSpeaker() {       ... ...省略       PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);       ... ...省略   }   这里最终交由PhoneUtils中的turnOnSpeaker去最终负责打开扬声器。 (4)时序图 图 11 开启/关闭扬声器 muteButton开启/关闭麦克风静音 麦克风静音的作用是在通话过程中,屏蔽自己这段的声音输入,在InCallTouchUi布局上显示为:。 控制流程 (1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <ToggleButton android:id="@+id/muteButton"                style="@style/InCallCompoundButton"                android:background="@drawable/btn_compound_mute"                android:contentDescription="@string/onscreenMuteText"/>   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java (3)工作流程 控件初始化代码如下: muteButton控件初始化: [java] view plaincopy private CompoundButton mMuteButton;   mMuteButton = (CompoundButton) mInCallControls.findViewById(R.id.muteButton);   mMuteButton.setOnClickListener(this);   mMuteButton.setOnLongClickListener(this);   这里的LongClick实际上是,在我们长按该控件时会弹出一个toast显示该控件的作用。找到muteButton的onClick实现方法,如下: [java] view plaincopy @Override   public void onClick(View view) {       int id = view.getId();       ... ...省略       switch (id) {           ... ...省略           case R.id.muteButton:           ... ...省略               mInCallScreen.handleOnscreenButtonClick(id);               break;           ... ...省略       }   }   依然调用了InCallScreen中的handleOnscreenButtonClick方法,代码如下: [java] view plaincopy public void handleOnscreenButtonClick(int id) {      ... ...省略      switch (id) {          ... ...省略          case R.id.muteButton:              onMuteClick();              break;          ... ...省略   然后调用onMuteClick方法来实现开启/关闭麦克风静音功能,代码如下: [java] view plaincopy public void onMuteClick() {       ... ...省略       PhoneUtils.setMute(newMuteState);       ... ...省略   }   最终的实现仍然在PhoneUtils中的setMute方法,该方法将继续传递直到audioManager去执行该静音操作。 (4)时序图 图 12 麦克风静音开启/关闭 holdButton开启/关闭呼叫保持 holdButton实际上就是呼叫保持的开关,用户可以暂停当前通话,在InCallTouchUi布局上显示为:。当用户在通话过程中点击该图标之后,对方将会听到类似提示音“请稍等,现在是呼叫保持……”,该功能用于在两路通话之间进行切换,但只有一路通话时无法开启呼叫保持功能。 控制流程 (1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <ToggleButton android:id="@+id/holdButton"                style="@style/InCallCompoundButton"                android:background="@drawable/btn_compound_hold"                android:contentDescription="@string/onscreenHoldText"/>   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java (3)工作流程 控件初始化代码如下: muteButton控件初始化: [java] view plaincopy private CompoundButton mHoldButton;   mHoldButton = (CompoundButton) mInCallControls.findViewById(R.id.holdButton);   mHoldButton.setOnClickListener(this);   mHoldButton.setOnLongClickListener(this);   这里的LongClick实际上是,在我们长按该控件时会弹出一个toast显示该控件的作用。找到holdButton的onClick实现方法,如下: [java] view plaincopy @Override   public void onClick(View view) {       int id = view.getId();       ... ...省略       switch (id) {           ... ...省略           case R.id.muteButton:           ... ...省略               mInCallScreen.handleOnscreenButtonClick(id);               break;           ... ...省略       }   }   依然调用了InCallScreen中的handleOnscreenButtonClick方法,代码如下: [java] view plaincopy public void handleOnscreenButtonClick(int id) {      ... ...省略      switch (id) {          ... ...省略          case R.id.holdButton:              onHoldClick();              break;          ... ...省略   然后调用onHoldClick方法来实现开启/关闭呼叫保持功能,代码如下: [java] view plaincopy private void onHoldClick() {       ... ...省略       if (hasActiveCall && !hasHoldingCall) {           // 开启呼叫保持           PhoneUtils.switchHoldingAndActive(               mCM.getFirstActiveBgCall());  // Really means "hold" in this state           newHoldState = true;           holdButtonEnabled = true;       } else if (!hasActiveCall && hasHoldingCall && !haveMultipleHoldingCall) {           // 取消呼叫保持           PhoneUtils.switchHoldingAndActive(               mCM.getFirstActiveBgCall());  // Really means "unhold" in this state           newHoldState = false;           holdButtonEnabled = true;       }        ... ...省略       // 强制关闭拨号盘       closeDialpadInternal(true);  // do the "closing" animation   }   继续追踪可以找到PhoneUtils中的switchHoldingAndActive方法,代码如下: [java] view plaincopy static void switchHoldingAndActive(Call heldCall) {       ... ...省略       try {           CallManager cm = PhoneGlobals.getInstance().mCM;           ... ...省略           cm.switchHoldingAndActive(heldCall);           ... ...省略   }   最终的实现会通过CallManager一层层的向下传递,并最终实现呼叫保持功能。 (4)时序图 图 13 呼叫保持开启/关闭 addButton添加一路通话 addButton的意思就是在当前通话的基础上添加一路通话,当前通话将会切换到呼叫保持状态。该图标在InCallTouchUi布局上显示为:。 当用户在当前通话过程中点击该图标之后,界面出现拨号盘,如果添加一路通话成功则会如下图14所示: 图 14 添加一路通话界面以及添加后界面 控制流程 (1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <ImageButton android:id="@+id/addButton"               style="@style/InCallButton"               android:src="@drawable/ic_add_contact_holo_dark"               android:contentDescription="@string/onscreenAddCallText"/>   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java (3)工作流程 控件初始化代码如下: addButton控件初始化: [java] view plaincopy private CompoundButton mAddButton;   mAddButton = (CompoundButton) mInCallControls.findViewById(R.id.addButton);   mAddButton.setOnClickListener(this);   mAddButton.setOnLongClickListener(this);   这里的LongClick实际上是,在我们长按该控件时会弹出一个toast显示该控件的作用。找到holdButton的onClick实现方法,如下: [java] view plaincopy @Override   public void onClick(View view) {       int id = view.getId();       ... ...省略       switch (id) {           ... ...省略           case R.id.addButton:           ... ...省略               mInCallScreen.handleOnscreenButtonClick(id);               break;           ... ...省略       }   }   依然调用了InCallScreen中的handleOnscreenButtonClick方法,代码如下: [java] view plaincopy public void handleOnscreenButtonClick(int id) {      ... ...省略      switch (id) {          ... ...省略          case R.id.addButton:              onAddCallClick();              break;          ... ...省略   然后调用onAddClick方法来实现增加一路通话的功能,代码如下: [java] view plaincopy private void onAddCallClick() {       PhoneUtils.startNewCall(mCM);   }   继续追踪可以找到PhoneUtils中的startNewCall方法,代码如下: [java] view plaincopy /* package */ static boolean startNewCall(final CallManager cm) {       final PhoneGlobals app = PhoneGlobals.getInstance();       ... ...省略       // 将当前的通话静音       if (cm.hasActiveFgCall()) {           setMuteInternal(cm.getActiveFgCall().getPhone(), true);           app.setRestoreMuteOnInCallResume(true);       }       Intent intent = new Intent(Intent.ACTION_DIAL);       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);       intent.putExtra(ADD_CALL_MODE_KEY, true);       try {           //跳转到拨号界面           app.startActivity(intent);       } catch (ActivityNotFoundException e) {           ... ...省略       }       return true;   }   需要注意的是,当我们点击addButton后,当前通话会开启静音功能,在我们拨打新一路通话时,先前的通话将会自动切换到呼叫保持状态。这里的Intent.ACTION_DIAL查找对应的具体字符串为“android.intent.action.DIAL”,查找对应的Activity找到Contacts中的NoPhoneActivity,该Activity用于加载拨号盘。 (4)时序图 图 15 新增一路通话时序图 endButton挂断当前通话 endButton就是挂断电话的那个按钮,点击之后将挂断当前通话。该图标在InCallTouchUi布局上显示为:。 控制流程 (1)布局文件 Phone/res/layout/incall_touch_ui.xml [html] view plaincopy <ImageButton android:id="@+id/endButton"               style="@style/InCallEndButton"               android:layout_width="match_parent"               android:layout_weight="1"               android:src="@drawable/ic_end_call"               android:background="@drawable/end_call_background"               android:contentDescription="@string/onscreenEndCallText" />   (2)初始化文件 Phone/src/com/android/phone/inCallTouchUi.java (3)工作流程 控件初始化代码如下: endButton控件初始化: [java] view plaincopy private CompoundButton mEndButton;   mEndButton = (CompoundButton) mInCallControls.findViewById(R.id.addButton);   mEndButton.setOnClickListener(this);   这里只注册了onClick的监听事件,没有LongOnClick的监听事件。找到endButton的onClick实现方法,如下: [java] view plaincopy @Override   public void onClick(View view) {       int id = view.getId();       ... ...省略       switch (id) {           ... ...省略           case R.id.endButton:           ... ...省略               mInCallScreen.handleOnscreenButtonClick(id);               break;           ... ...省略       }   }   依然调用了InCallScreen中的handleOnscreenButtonClick方法,代码如下: [java] view plaincopy public void handleOnscreenButtonClick(int id) {      ... ...省略      switch (id) {          ... ...省略          case R.id.endButton:              internalHangup();              break;          ... ...省略   然后调用internalHangup方法来实现挂断当前通话,代码如下: [java] view plaincopy private void internalHangup() {       ... ...省略       PhoneUtils.hangup(mCM);       ... ...省略       }   }   继续追踪可以找到PhoneUtils中的hangup方法,代码如下: [java] view plaincopy public static boolean hangup(CallManager cm) {       ... ...省略           ringing = cm.getFirstActiveRingingCall();           fg = cm.getActiveFgCall();           bg = cm.getFirstActiveBgCall();       ... ...省略       //因为选择的是挂断当前通话,因此fg.isIdle()为false       } else if (!fg.isIdle() || fg.state == Call.State.DISCONNECTING) {           if (DBG) log("hangup(): hanging up foreground call");           hungup = hangup(fg);       ... ...省略   }   这里我们需要注意,fg=cm.getActiveFgCall返回类型是Call类型的,这里的hangup(fg)实际处理代码为: [java] view plaincopy static boolean hangup(Call call) {       ... ...省略       //挂断当前通话       call.hangup();       ... ...省略   }   到了这里需要注意下,这里的call对象是什么呢?因为我们这里使用的是GSM卡(WCDMA卡也一样),因此这里我们实际上得到的call对象是GsmCall.java的对象,从而直接找到GsmCall中的hangup方法,代码如下: [java] view plaincopy public void   hangup() throws CallStateException {       owner.hangup(this);   }   这里最终会调用到GsmCallTracker中的hangup(GsmCall)方法中去,这里就不详解了。 (4)时序图 图 16 挂断当前通话时序图 InCallScreen CallCard通话信息显示 CallCard说起来感觉很陌生,实际上在我们通话过程中,除了InCallTouchUi之外,InCallScreen所展示的界面就是CallCard了,如图17: 图 17 CallCard通话信息 如图17所显示,CallCard包含了通话时间、通话背景、通话对象号码(10086)、来电/去电状态、号码归属地、SIM卡运营商类型。 CallCard布局文件 通话信息界面实际上也在incall_screen.xml的布局中,但实际引用的布局为call_card.xml,整个call_card的布局较为复杂,因为通话可以是一路通话但也可以是两路通话,可以是来电也可以是去电,因此CallCard的布局较为繁杂,通过Hierarchy Viewer可以查看到。 CallCard界面更新 因为CallCard界面主要用于告知用户当前的通话状态,主要反映的是一些状态信息,因此我们主要查看其更新的代码,在InCallScreen的onResume中,我们可以看到有关于同步Phone状态的代码如下: [java] view plaincopy SyncWithPhoneStateStatus status = syncWithPhoneState();   而这里的syncWithPhoneState方法就是用于同步Phone的状态,代码如下: [java] view plaincopy private SyncWithPhoneStateStatus syncWithPhoneState() {       ... ...省略       if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()               || hasPendingMmiCodes || hasPendingMmiCodes2 || showProgressIndication || showScreenEvenAfterDisconnect) {           if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");           updateScreen();           return SyncWithPhoneStateStatus.SUCCESS;       }       ... ...省略   }   这里会执行到updateScreen方法中,继续查看代码: [java] view plaincopy private void updateScreen() {       ... ...省略       mCallCard.updateState(mCM);       ... ...省略   }   因为这里我们主要关注CallCard的更新,其它内容就省略掉了。这里调用了CallCard的对象mCallCard,并调用其中的updataState方法。如下: [java] view plaincopy     /* package */ void updateState(CallManager cm) {           ... ...省略               //更新来电信息               updateRingingCall(cm);           ... ...省略               //更新当前通话信息               updateForegroundCall(cm);           ... ...省略               //更新已断开连接的通话信息               updateAlreadyDisconnected(cm);           ... ...省略               //更新没有通话的界面信息               updateNoCall(cm);           ... ...省略   }   在updateState中会根据不同的条件选择更新不同的界面,从而在InCallScreen中展示不同的结果。有以下四个方法用于更新不同状态下的CallCard信息: updateRingingCall:显示/更新来电界面信息; updateForegroundCall:显示/更新当前通话界面信息;updateAlreadyDisconnected:更新/显示已断开连接的通话界面信息,该界面只是一瞬间状态; updateNoCall:更新/显示没有通话时的通话界面信息;该界面一般不会出现,只为以防万一; CallCard更新时序图 图18 CallCard更新时序图 小结 在MTK的Android 4.2平台上,InCallScreen相对于原生的界面改动不算大,但其也增加了一些属于自己的东西,如:视屏通话界面,双卡控制界面,来电归属地等等,这些功能的添加使得Android手机在使用上更加便捷,也增加了用户体验。 在弄清楚了这界面UI的控制流程之后,对于修改InCallScreen界面有很大帮助,便于后续对InCallScreen进行个性化定制。 本文旨在分析InCallScreen上的UI控制流程,基于MTK Android 4.2的源码分析,其中不乏缺漏之处还恳请各位看官见谅。

上一篇:异步通知
下一篇:Linux 获取本机IP地址和MAC地址(二)

相关文章

相关评论