图1中所示就是InCallScreen不同情况下所展示的界面,分别是拨号、接通、来电三种情况下InCallScreen的现实情况。
经过对比后可以发现,InCallScreen的拨号以及接通时,界面表现基本一致,而来电界面主要由于多了一个滑动接听的控件,从而导致界面不太一样。这里我们队InCallScreen的结构分析,采用接通之后的界面。如图2:
在InCallScreen.java中,我们可以在onCreate方法中找到InCallScreen加载的布局文件,即incall_screen.xml。在incall_screen.java文件中,我们可以看到有以下几个控件:
因为我们这里主要分析一般模式下的通话界面,因此暂不涉及到视屏通话。
总的来讲,在图2显示的界面中,我们直观能够看到的控件主要是:call_card以及incall_touch_ui这两块。当我们点击DTMF弹出按钮之后,会显示DTMF控件。当然在我们接入多方通话之后,就会看到多方通话的界面了。
通过查看Phone.apk的AndroidManifest.xml文件可以看到:
android:label="@string/phoneIconLabel"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:screenOrientation="nosensor"
android:configChanges="keyboardHidden"
android:exported="false">
查看@style/Theme.InCallScreen可以看到:
可以看到InCallScreen的Theme中没有ActionBar,窗口背景为黑色,有过场动画。
因为InCallScreen为单例模式,第一次启动时调用onCreate而后面则会调用其onNewIntent方法。我们知道在onCreate方法中,一般都是对一些对象进行创建并初始化,以及设置布局文件等等。在InCallScreen的onCreate方法中,完成了PhoneApp对象的获取,以及Window参数的设置等等,在这些过程中我们需要关注以下三个方法:internalResolveIntent:该方法用于处理InCallScreen收到的Intent信息;
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);
}
private void initInCallTouchUi() {
... ...省略
mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
mInCallTouchUi.setInCallScreenInstance(this);
// 挂断并通话短信回复
mRespondViaSmsManager = new RespondViaSmsManager();
mRespondViaSmsManager.setInCallScreenInstance(this);
}
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);
... ...省略
}
该方法是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;
}
}
1)布局文件
Phone/res/layout/incall_touch_ui.xml
(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);
}
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 来电接听时序图
图 8 DTMF拨号盘
(1)布局文件
Phone/res/layout/incall_touch_ui.xml
Phone/res/layout/incall_screen.xml
(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);
@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;
... ...省略
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 接通后显示拨号盘时序图
图 10 接入蓝牙耳机后InCallTouchUi上audioButton改变
我可以选择三种不同的音频输出方式:Speaker即扬声器,Handset earpiece手机听筒,Bluetooth蓝牙耳机。
(1)布局文件
Phone/res/layout/incall_touch_ui.xml
(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);
@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);
... ...省略
}
(1)布局文件
Phone/res/layout/incall_touch_ui.xml
(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)时序图
(1)布局文件
Phone/res/layout/incall_touch_ui.xml
(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)时序图
图 14 添加一路通话界面以及添加后界面
(1)布局文件
Phone/res/layout/incall_touch_ui.xml
(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)时序图
(1)布局文件
Phone/res/layout/incall_touch_ui.xml
(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)时序图
图 17 CallCard通话信息
如图17所显示,CallCard包含了通话时间、通话背景、通话对象号码(10086)、来电/去电状态、号码归属地、SIM卡运营商类型。
通话信息界面实际上也在incall_screen.xml的布局中,但实际引用的布局为call_card.xml,整个call_card的布局较为复杂,因为通话可以是一路通话但也可以是两路通话,可以是来电也可以是去电,因此CallCard的布局较为繁杂,通过Hierarchy Viewer可以查看到。
因为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信息:
在MTK的Android 4.2平台上,InCallScreen相对于原生的界面改动不算大,但其也增加了一些属于自己的东西,如:视屏通话界面,双卡控制界面,来电归属地等等,这些功能的添加使得Android手机在使用上更加便捷,也增加了用户体验。
在弄清楚了这界面UI的控制流程之后,对于修改InCallScreen界面有很大帮助,便于后续对InCallScreen进行个性化定制。
本文旨在分析InCallScreen上的UI控制流程,基于MTK Android 4.2的源码分析,其中不乏缺漏之处还恳请各位看官见谅。