InCallScreen是什麼
圖1中所示就是InCallScreen不同情況下所展示的界面,分別是撥號、接通、來電三種情況下InCallScreen的現實情況。
InCallScreen結構分析
經過對比後可以發現,InCallScreen的撥號以及接通時,界面表現基本一致,而來電界面主要由於多了一個滑動接聽的控件,從而導致界面不太一樣。這裏我們隊InCallScreen的結構分析,採用接通之後的界面。如圖2:
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通話信息展示
- <?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>
整個佈局情況如下圖:
incall_touch_ui通話控制界面
InCallScreen初始化流程
通過查看Phone.apk的AndroidManifest.xml文件可以看到:
- <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>
查看@style/Theme.InCallScreen可以看到:
- <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信息;
initInCallScreen
- 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初始化的代碼:
- private void initInCallTouchUi() {
- ... ...省略
- mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
- mInCallTouchUi.setInCallScreenInstance(this);
- // 掛斷並通話短信回覆
- mRespondViaSmsManager = new RespondViaSmsManager();
- mRespondViaSmsManager.setInCallScreenInstance(this);
- }
通過代碼可以知道InCallScreen上的佈局顯示幾乎都是在initInCallScreen方法中做的,如果我們修改了InCallScreen的佈局那麼我們應該在這裏對修改後的佈局進行初始化。
registerForPhoneStates
- 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的方法,代碼如下:
- 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初始化小結
InCallScreenUI控制流程
- incomingCallWidget:接通/掛斷/短信回覆時需要使用;
- dialpadButton:也就是顯示或隱藏撥號盤(DTMF);
- audioButton:開啓/關閉揚聲器;
- muteButton:開啓/關閉麥克風靜音,開啓之後對方無法聽到你的聲音;
- holdButton:開啓/關閉呼叫保持;
- addButton:增加多路通話;也就是在通話的過程中可以暫停當前通話,撥打另一路通話並接通;
incomingCallWidget滑動控件
該滑動控件在來電的時候,會顯示在InCallScreen界面上,默認情況下用戶可以選擇接聽、掛斷、掛斷並短信回覆三種模式,向右滑動爲接聽,向左滑動爲掛斷,向上滑動爲掛斷並選擇快捷短信回覆,如圖6:
控制流程
1)佈局文件
Phone/res/layout/incall_touch_ui.xml
- <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)工作流程
首先初始化,代碼如下:
- private GlowPadView mIncomingCallWidget;
- mIncomingCallWidget = (GlowPadView) findViewById(R.id.incomingCallWidget);
- mIncomingCallWidget.setOnTriggerListener(this);
通過初始化代碼我們可以看到其註冊了一個TriggerListener並且就在本類中就有其實現,那麼我繼續找到其TriggerListener的實現,代碼如下:
- /**
- * 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方法去執行具體代碼,如下:
- 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顯示/隱藏撥號盤
該控件的作用是點擊之後顯示或隱藏撥號盤,當電話接通之後如果用戶點擊該控件則會如圖8所示:
圖 8 DTMF撥號盤
控制流程
(1)佈局文件
Phone/res/layout/incall_touch_ui.xml- <ToggleButton android:id="@+id/dialpadButton"
- style="@style/InCallCompoundButton"
- android:background="@drawable/btn_dialpad"
- android:contentDescription="@string/onscreenShowDialpadText"/>
Phone/res/layout/incall_screen.xml
- <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控件初始化:
- private CompoundButton mDialpadButton;
- mDialpadButton = (CompoundButton) mInCallControls.findViewById(R.id.dialpadButton);
- mDialpadButton.setOnClickListener(this);
- mDialpadButton.setOnLongClickListener(this);
這裏的LongClick實際上是,在我們長按該控件時會彈出一個toast顯示該控件的作用。
撥號盤控件初始化:
- private DTMFTwelveKeyDialer mDialer;
- ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
- mDialer = new DTMFTwelveKeyDialer(this, stub);
我們先找到mDialpadButton的onClick實現方法,如下:
- @Override
- public void onClick(View view) {
- int id = view.getId();
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.dialpadButton:
- ... ...省略
- mInCallScreen.handleOnscreenButtonClick(id);
- break;
- ... ...省略
- }
- }
這裏還是調用了InCallScreen中的handleOnscreenButtonClick方法,代碼如下:
- public void handleOnscreenButtonClick(int id) {
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.dialpadButton:
- onOpenCloseDialpad();
- break;
- ... ...省略
這裏最終調用onOpenCloseDialpad方法去實現打開或者關閉撥號盤,這裏我們繼續查看onOpenCloseDialpad方法,代碼如下:
- 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代碼如下:
- public void openDialer(boolean animate) {
- ... ...省略
- if (!isOpened()) {
- // 這裏animate=true
- if (animate) {
- AnimationUtils.Fade.show(mDialerView);
- ... ...省略
- }
查看AnimationUtils.Fade.show方法如下:
- public static void show(final View view) {
- ... ...省略
- view.setVisibility(View.VISIBLE);
- ... ...省略
- }
最終使得mDialerView顯示到界面上。
(4)時序圖
圖 9 接通後顯示撥號盤時序圖
audioButton開啓/關閉揚聲器
該控件的作用是點擊之後開啓或關閉揚聲器,也就是使得對方的通話聲音能夠通過外放增大。如果我們此時連接了藍牙耳機,那麼顯示界面如圖10所示:
圖 10 接入藍牙耳機後InCallTouchUi上audioButton改變
我可以選擇三種不同的音頻輸出方式:Speaker即揚聲器,Handset earpiece手機聽筒,Bluetooth藍牙耳機。
控制流程
(1)佈局文件
Phone/res/layout/incall_touch_ui.xml
- <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控件初始化:
- private CompoundButton mAudioButton;
- mAudioButton = (CompoundButton) mInCallControls.findViewById(R.id.audioButton);
- mAudioButton.setOnClickListener(this);
- mAudioButton.setOnLongClickListener(this);
這裏的LongClick實際上是,在我們長按該控件時會彈出一個toast顯示該控件的作用。找到audioButton的onClick實現方法,如下:
- @Override
- public void onClick(View view) {
- int id = view.getId();
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.audioButton:
- ... ...省略
- handleAudioButtonClick();
- break;
- ... ...省略
- }
- }
這裏的handleAudioButtonClick方法對是否接入了藍牙耳機進行了判斷,如果是則會像圖9所示那樣,彈出三個選項按鈕。這裏我們假設沒有連接藍牙耳機,代碼如下:
- private void handleAudioButtonClick() {
- ... ...省略
- //如果連接了藍牙耳機則執行if裏面的代碼
- if (inCallControlState.bluetoothEnabled) {
- ... ...省略
- } else {
- ... ...省略
- mInCallScreen.toggleSpeaker();
- }
- }
這裏我們繼續查看InCallScreen中的toggleSpeaker方法:
- public void toggleSpeaker() {
- ... ...省略
- PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
- ... ...省略
- }
這裏最終交由PhoneUtils中的turnOnSpeaker去最終負責打開揚聲器。
(4)時序圖
muteButton開啓/關閉麥克風靜音
控制流程
(1)佈局文件
Phone/res/layout/incall_touch_ui.xml
- <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控件初始化:
- private CompoundButton mMuteButton;
- mMuteButton = (CompoundButton) mInCallControls.findViewById(R.id.muteButton);
- mMuteButton.setOnClickListener(this);
- mMuteButton.setOnLongClickListener(this);
這裏的LongClick實際上是,在我們長按該控件時會彈出一個toast顯示該控件的作用。找到muteButton的onClick實現方法,如下:
- @Override
- public void onClick(View view) {
- int id = view.getId();
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.muteButton:
- ... ...省略
- mInCallScreen.handleOnscreenButtonClick(id);
- break;
- ... ...省略
- }
- }
依然調用了InCallScreen中的handleOnscreenButtonClick方法,代碼如下:
- public void handleOnscreenButtonClick(int id) {
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.muteButton:
- onMuteClick();
- break;
- ... ...省略
然後調用onMuteClick方法來實現開啓/關閉麥克風靜音功能,代碼如下:
- public void onMuteClick() {
- ... ...省略
- PhoneUtils.setMute(newMuteState);
- ... ...省略
- }
最終的實現仍然在PhoneUtils中的setMute方法,該方法將繼續傳遞直到audioManager去執行該靜音操作。
(4)時序圖
holdButton開啓/關閉呼叫保持
控制流程
(1)佈局文件
Phone/res/layout/incall_touch_ui.xml
- <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控件初始化:
- private CompoundButton mHoldButton;
- mHoldButton = (CompoundButton) mInCallControls.findViewById(R.id.holdButton);
- mHoldButton.setOnClickListener(this);
- mHoldButton.setOnLongClickListener(this);
這裏的LongClick實際上是,在我們長按該控件時會彈出一個toast顯示該控件的作用。找到holdButton的onClick實現方法,如下:
- @Override
- public void onClick(View view) {
- int id = view.getId();
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.muteButton:
- ... ...省略
- mInCallScreen.handleOnscreenButtonClick(id);
- break;
- ... ...省略
- }
- }
依然調用了InCallScreen中的handleOnscreenButtonClick方法,代碼如下:
- public void handleOnscreenButtonClick(int id) {
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.holdButton:
- onHoldClick();
- break;
- ... ...省略
然後調用onHoldClick方法來實現開啓/關閉呼叫保持功能,代碼如下:
- 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方法,代碼如下:
- static void switchHoldingAndActive(Call heldCall) {
- ... ...省略
- try {
- CallManager cm = PhoneGlobals.getInstance().mCM;
- ... ...省略
- cm.switchHoldingAndActive(heldCall);
- ... ...省略
- }
最終的實現會通過CallManager一層層的向下傳遞,並最終實現呼叫保持功能。
(4)時序圖
addButton添加一路通話
當用戶在當前通話過程中點擊該圖標之後,界面出現撥號盤,如果添加一路通話成功則會如下圖14所示:
圖 14 添加一路通話界面以及添加後界面
控制流程
(1)佈局文件
Phone/res/layout/incall_touch_ui.xml
- <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控件初始化:
- private CompoundButton mAddButton;
- mAddButton = (CompoundButton) mInCallControls.findViewById(R.id.addButton);
- mAddButton.setOnClickListener(this);
- mAddButton.setOnLongClickListener(this);
這裏的LongClick實際上是,在我們長按該控件時會彈出一個toast顯示該控件的作用。找到holdButton的onClick實現方法,如下:
- @Override
- public void onClick(View view) {
- int id = view.getId();
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.addButton:
- ... ...省略
- mInCallScreen.handleOnscreenButtonClick(id);
- break;
- ... ...省略
- }
- }
依然調用了InCallScreen中的handleOnscreenButtonClick方法,代碼如下:
- public void handleOnscreenButtonClick(int id) {
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.addButton:
- onAddCallClick();
- break;
- ... ...省略
然後調用onAddClick方法來實現增加一路通話的功能,代碼如下:
- private void onAddCallClick() {
- PhoneUtils.startNewCall(mCM);
- }
繼續追蹤可以找到PhoneUtils中的startNewCall方法,代碼如下:
- /* 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)時序圖
endButton掛斷當前通話
控制流程
(1)佈局文件
Phone/res/layout/incall_touch_ui.xml
- <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控件初始化:
- private CompoundButton mEndButton;
- mEndButton = (CompoundButton) mInCallControls.findViewById(R.id.addButton);
- mEndButton.setOnClickListener(this);
這裏只註冊了onClick的監聽事件,沒有LongOnClick的監聽事件。找到endButton的onClick實現方法,如下:
- @Override
- public void onClick(View view) {
- int id = view.getId();
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.endButton:
- ... ...省略
- mInCallScreen.handleOnscreenButtonClick(id);
- break;
- ... ...省略
- }
- }
依然調用了InCallScreen中的handleOnscreenButtonClick方法,代碼如下:
- public void handleOnscreenButtonClick(int id) {
- ... ...省略
- switch (id) {
- ... ...省略
- case R.id.endButton:
- internalHangup();
- break;
- ... ...省略
然後調用internalHangup方法來實現掛斷當前通話,代碼如下:
- private void internalHangup() {
- ... ...省略
- PhoneUtils.hangup(mCM);
- ... ...省略
- }
- }
繼續追蹤可以找到PhoneUtils中的hangup方法,代碼如下:
- 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)實際處理代碼爲:
- static boolean hangup(Call call) {
- ... ...省略
- //掛斷當前通話
- call.hangup();
- ... ...省略
- }
到了這裏需要注意下,這裏的call對象是什麼呢?因爲我們這裏使用的是GSM卡(WCDMA卡也一樣),因此這裏我們實際上得到的call對象是GsmCall.java的對象,從而直接找到GsmCall中的hangup方法,代碼如下:
- public void
- hangup() throws CallStateException {
- owner.hangup(this);
- }
這裏最終會調用到GsmCallTracker中的hangup(GsmCall)方法中去,這裏就不詳解了。
(4)時序圖
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狀態的代碼如下:
- SyncWithPhoneStateStatus status = syncWithPhoneState();
而這裏的syncWithPhoneState方法就是用於同步Phone的狀態,代碼如下:
- 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方法中,繼續查看代碼:
- private void updateScreen() {
- ... ...省略
- mCallCard.updateState(mCM);
- ... ...省略
- }
因爲這裏我們主要關注CallCard的更新,其它內容就省略掉了。這裏調用了CallCard的對象mCallCard,並調用其中的updataState方法。如下:
- /* package */ void updateState(CallManager cm) {
- ... ...省略
- //更新來電信息
- updateRingingCall(cm);
- ... ...省略
- //更新當前通話信息
- updateForegroundCall(cm);
- ... ...省略
- //更新已斷開連接的通話信息
- updateAlreadyDisconnected(cm);
- ... ...省略
- //更新沒有通話的界面信息
- updateNoCall(cm);
- ... ...省略
- }
在updateState中會根據不同的條件選擇更新不同的界面,從而在InCallScreen中展示不同的結果。有以下四個方法用於更新不同狀態下的CallCard信息:
- updateRingingCall:顯示/更新來電界面信息;
- updateForegroundCall:顯示/更新當前通話界面信息;
- updateAlreadyDisconnected:更新/顯示已斷開連接的通話界面信息,該界面只是一瞬間狀態;
- updateNoCall:更新/顯示沒有通話時的通話界面信息;該界面一般不會出現,只爲以防萬一;
CallCard更新時序圖
小結
在MTK的Android 4.2平臺上,InCallScreen相對於原生的界面改動不算大,但其也增加了一些屬於自己的東西,如:視屏通話界面,雙卡控制界面,來電歸屬地等等,這些功能的添加使得Android手機在使用上更加便捷,也增加了用戶體驗。
在弄清楚了這界面UI的控制流程之後,對於修改InCallScreen界面有很大幫助,便於後續對InCallScreen進行個性化定製。
本文旨在分析InCallScreen上的UI控制流程,基於MTK Android 4.2的源碼分析,其中不乏缺漏之處還懇請各位看官見諒。