6572 Phone call分析

目 录

1. 概述 4

1.1 Phone Call 4

1.1.1 框架介绍 4

1.1.2 功能说明 5

2. 4.2呼叫相关类分析 6

2.1 基本知识 6

2.1.1 文件说明 6

2.2 DialtactsActivity 7

2.2.1 onCreate 7

2.2.2 其他 10

2.3 CallManager 11

2.3.1 说明 11

2.3.2 初始注册 12

2.3.3 业务注册 13

2.4 CallNotifier 15

2.4.1 说明 15

2.5 PhoneInterfaceManager 17

2.5.1 说明 17

2.6 NotificationMgr 17

2.6.1 说明 17

2.7 InCallScreen 18

2.7.1 说明 18

3. ICS Android 4.2 呼叫流程 21

3.1 呼叫请求 21

3.1.1 拨号请求(Contact) 21

3.1.2 拨号请求(Phone) 26

3.1.3 进入通话界面 30

3.2 呼叫建立 34

3.2.1 ECPI 34

3.2.2 ECPI消息处理 37

3.2.3 界面更新 40

3.3 呼叫结束 40

3.3.1 ECPI消息处理 40

3.3.2 界面更新 42

3.4 MO呼叫流程图 42

3.5 MT呼叫 43

3.5.1 接听 43

3.5.2 拒接 43

3.6 挂断呼叫 43

1. 
概述

1.1 Phone Call

(本文在文字描述和图片引用方面,使用了其他同学的部分内容,在此感谢!本人侧重代码分析,在此将分析过程分享给大家。)

1.1.1 框架介绍

...

在Telephony层的相关类关系如下:

1.1.1 功能说明

GsmCallTracker对象中还提供有三个GsmCall对象(派生自抽象类Call):ringingCall(用来管理处于INCOMING和WAITING状态的通话)、foregroundCall(用来管理处于DAILING、ALERTING、ACTIVE状态的通话)、backgroundCall(用来管理HOLD的通话)。

GsmCallTracker是Call应用中的通话管理层,它维护了一个最多MAX_CONNECTIONS=7路GsmConnections通话链路的同时,还维护了三种通话状态(ringingCall,foregroundCall,backgroundCall),GsmCall以及GsmConnection是GsmCallTracker维护的对象,同时GsmConnection又依附于GsmCall的存在,MAX_CONNECTIONS_PER_CALL=5表明最多可以有5路通话处于某一个通话状态(foregroundCall,background,ringing)。GsmConnection继承自Connection类,该类主要是用来维护某一路的通话状态。当它们状态均为DISCONNECTED时意味着该GSMCall为IDLE状态。

在GSMCallTracker中维护着通话列表:connections。顺序记录了正连接上的通话,这些通话包括:ACTIVE,DIALING,ALERTING,HOLDING,INCOMING,WAITING等状态的连接。GSMCallTracker将这些连接分为了三类别进行管理:RingingCall: INCOMING ,WAITING  ForegourndCall: ACTIVE, DIALING ,ALERTING BackgroundCall: HOLDING

另外GSMCallTracker还包含一个GsmConnection类型(派生自抽象类Connection)的数组对象Connections,用来维护所有的现行的通话的列表,GSMCallTracker对象最大可维护7路通话。

GsmConnection对象中有个成员变量:GsmCall parent,这个成员变量是用来表示该connection是属于哪个Call的,一个Call可以有多个Connection,但一个Connection只能属于一个Call。

1. 4.2呼叫相关类分析

1.1 基本知识

1.1.1 文件说明

CallDetailActivity.java 联系人详情

DialtactsActivity.java  通话记录

VoicemailContract.java 存放VM相关的一些常量、URI等,子类Status会经常被引用。

在calllogfragment.java  通话记录列表里添加了vvm的记录。

在CallDetailActivity.java 联系人详情里添加了详细信息显示。

在CallDetailHistoryAdapter.java里做详细信息数据适配器

DialpadFragment.java 拨号盘界面

CallNotifier是一个Handler,它为PhoneApp处理各个主动上报来的一些消息。它监听来自Telephony层phone状态变化和其它各种事件,从而作出反应 如各种UI行为:启动铃音播放和来电显示UI、播放正在通话时的来电提示、更新状态栏提示(通过NotificationMgr)、通话记录添加等。

1.2 DialtactsActivity

DialtactsActivity.java  Dialer程序在packages/apps/Contacts中,见DialtactsActivity,它继承自TabActivity类, 是通话界面入口activity,包含3个功能项,由3个tab呈现, 通过实现接口 TabHost.OnTabChangeListener在各个tab之间切换。界面如下,

1.1.1 onCreate

onCreate主要完成了如下注释的几个工作,

protected void onCreate(Bundle icicle) {

final Intent intent = getIntent();

fixIntent(intent);

setContentView(R.layout.dialtacts_activity); //载入layout,该layout修改后只有一个view pager

mContactListFilterController = ContactListFilterController.getInstance(this);

mContactListFilterController.addListener(mContactListFilterListener);

findViewById(R.id.dialtacts_frame).addOnLayoutChangeListener(mFirstLayoutListener);

mViewPager = (ViewPager) findViewById(R.id.pager);//获取view的控件引用,并进行相关设置

mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));//getFragmentManager在activity里面,该句详细分析见后

mViewPager.setOnPageChangeListener(mPageChangeListener); // 监听器设置当前文件只改变了索引

mViewPager.setOffscreenPageLimit(2);

addDialpadScrollingThreshold(true);

// Setup the ActionBar tabs (the order matches the tab-index contants TAB_INDEX_*)

setupDialer();// 设置各功能项,当前只是添加了tab,实现在其他地方

setupCallLog();

setupFavorites();

getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

getActionBar().setDisplayShowTitleEnabled(false);

getActionBar().setDisplayShowHomeEnabled(false);

setCurrentTab(intent);

}

1)布局文件

dialtacts_activity.xml

<android.support.v4.view.ViewPager

android:id="@+id/pager"

android:layout_width="match_parent"

android:layout_height="match_parent" />

2)ViewPager

根据onCreate以及后面的start等方法,我们并没有明显发现各tab项对应的界面布局和相关数据处理,而且我们的layout也仅仅是一个ViewPager类,我们需要找到tab的实现过程。

通过代码分析我们可以看出mDialpadFragment、mCallLogFragment、mPhoneFavoriteFragment是各tab对应的类的引用,他们通过onAttachFragment被赋值,通过子类ViewPagerAdapter的方法getItem来new出各自的实例。

public void onAttachFragment(Fragment fragment) {

if (fragment instanceof DialpadFragment) {

mDialpadFragment = (DialpadFragment) fragment;

} else if (fragment instanceof CallLogFragment) {

mCallLogFragment = (CallLogFragment) fragment;

} else if (fragment instanceof PhoneFavoriteFragment) {

mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;

}

ViewPagerAdapter:

public Fragment getItem(int position) {

switch (position) {

case TAB_INDEX_DIALER:

return new DialpadFragment();

case TAB_INDEX_CALL_LOG:

return new CallLogFragment();

case TAB_INDEX_FAVORITES:

return new PhoneFavoriteFragment();

}

这些方法是如何关联上的,各类之间的关系是怎样的,我们可以进一步来分析。

根据上面onCreate的代码可以看出,通过layout,我们得到了一个ViewPager的引用,然后设置adapter,在setAdapter里调用populate,populate调用addNewItem,addNewItem调用mAdapter.instantiateItem,而getItem则是被instantiateItem调用的,所以我们能知道通过getItem最终将new出来的实例传递给了ViewPager。

对于其中的mAdapter,则是onCreate新建的子类ViewPagerAdapter的实例,通过它的继承关系,可以看到getItem是被父类FragmentPagerAdapter的instantiateItem调用的。

public Object instantiateItem(ViewGroup container, int position) {

fragment = getItem(position);

onAttachFragment是在new Fragment的时候被调用的,它是在Fragment.onAttach()后并在Fragment.onCreate()前被调用的。

这样,mDialpadFragment等对象实例就初始化好了,并通过一些粘合的方法关联到tab上了。

3)setupDialer

添加tab项。以setupDialer为例,说明tab是如何添加的,这里需要注意的是对mTabListener的设置,关联了ViewPager和tab。

private void setupDialer() {

final Tab tab = getActionBar().newTab();

tab.setContentDescription(R.string.dialerIconLabel);

tab.setTabListener(mTabListener);

tab.setIcon(R.drawable.ic_tab_dialer);

getActionBar().addTab(tab);

}

4)

如果启动了contact进程,从launcher里面进入到dailtactsactivity,不会再走onCreate,而是先走onNewIntent,再走start。

进程如何启动,为何不走onCreate?

1.1.2 其他

onNewIntent

在其他应用使用拨号,调用onNewIntent,调用栈如下,

DialtactsActivity.onNewIntent(Intent) line: 1135

Instrumentation.callActivityOnNewIntent(Activity, Intent) line: 1156

ActivityThread.deliverNewIntents(ActivityThread$ActivityClientRecord, List) line: 2493

ActivityThread.performNewIntents(IBinder, List) line: 2506

ActivityThread.handleNewIntent(ActivityThread$NewIntentData) line: 2515

ActivityThread.access$1400(ActivityThread, ActivityThread$NewIntentData) line: 162

ActivityThread$H.handleMessage(Message) line: 1435

ActivityThread$H(Handler).dispatchMessage(Message) line: 107

Looper.loop() line: 194

ActivityThread.main(String[]) line: 5400

Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]

Method.invoke(Object, Object...) line: 525

ZygoteInit$MethodAndArgsCaller.run() line: 837

ZygoteInit.main(String[]) line: 604

NativeStart.main(String[]) line: not available [native method]

setCurrentTab

设置当前tab关联的viewPage对象。这个方法是tab和view的粘合方法。

从mainmenu进入DialpadFragment的调用栈,

DialpadFragment.onStart() line: 1043

DialpadFragment(Fragment).performStart() line: 1719

FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 913

FragmentManagerImpl.moveToState(int, int, int, boolean) line: 1057

FragmentManagerImpl.moveToState(int, boolean) line: 1039

FragmentManagerImpl.dispatchStart() line: 1845

DialtactsActivity(Activity).performStart() line: 5138

DialtactsActivity(Activity).performRestart() line: 5187

DialtactsActivity(Activity).performResume() line: 5192

1.1 CallManager

1.1.1 说明

* CallManager class provides an abstract layer for PhoneApp to access

* and control calls. It implements Phone interface.

*

* CallManager provides call and connection control as well as

* channel capability.

*

* There are three categories of APIs CallManager provided

*

*  1. Call control and operation, such as dial() and hangup()

*  2. Channel capabilities, such as CanConference()

*  3. Register notification

如上描述,CallManager提供Phone应用的抽象层处理,主要提供3类接口API:呼叫控制、信道信息、注册通知。

1.1.2 初始注册

CallManager提供registerPhone来进行相关实例的初始注册。

PhoneGlobals.java在onCreate里面调用自身方法registerPhone,间接调用CallManager.registerPhone来注册相关实例。

private void registerPhone() {

mCM = CallManager.getInstance();

if (GeminiUtils.isGeminiSupport()) {

mCMGemini = MTKCallManager.getInstance();

mCMGemini.registerPhoneGemini(phone);

} else {

mCM.registerPhone(phone);

}

}

PhoneProxy.java里在重建phone对象时也会进行registerPhone注册。

deleteAndCreatePhone()

{

if(mActivePhone != null)

CallManager.getInstance().registerPhone(mActivePhone);

}

对于CallManager registerPhone的实现,如下,为每个卡槽实例提供呼叫相关的注册,

public boolean registerPhone(Phone phone) {

if ((FeatureOption.MTK_BSP_PACKAGE == true) &&

(FeatureOption.MTK_GEMINI_SUPPORT == true) &&

(!(phone instanceof SipPhone))) {

int count = (MAXIMUM_SIM_COUNT < PhoneConstants.GEMINI_SIM_NUM) ? MAXIMUM_SIM_COUNT : PhoneConstants.GEMINI_SIM_NUM;

/* registerPhone is called by Google default PhoneAPP */

for (int i = 0; i < count; i++) {

Phone p = ((GeminiPhone)phone).getPhonebyId(PhoneConstants.GEMINI_SIM_1 + i);

registerOnePhone(p);

}

int default_sim = SystemProperties.getInt(PhoneConstants.GEMINI_DEFAULT_SIM_PROP, PhoneConstants.GEMINI_SIM_1);

mDefaultPhone = getPhoneBase(((GeminiPhone)phone).getPhonebyId(default_sim));

Log.d(LOG_TAG, "[BSPPackage]default_sim = " + default_sim);

Log.d(LOG_TAG, "[BSPPackage]mDefaultPhone = " + mDefaultPhone);

registerForPhoneStates(getPhoneBase(phone));

return true;

每个卡槽实例都注册mPhones、mRingingCalls、mBackgroundCalls、mForegroundCalls。

private boolean registerOnePhone(Phone phone) {

boolean result = false;

Phone basePhone = getPhoneBase(phone);

if (basePhone != null && !mPhones.contains(basePhone)) {

mPhones.add(basePhone);

mRingingCalls.add(basePhone.getRingingCall());

mBackgroundCalls.add(basePhone.getBackgroundCall());

mForegroundCalls.add(basePhone.getForegroundCall());

result = true;

}

return result;

}

1.1.3 业务注册

1)向上提供注册服务

CallManager通过向应用的上层提供注册服务的方法,将呼叫信息和网络状态上报。

常规的注册方法是GeminiRegister做中转,向上层提供注册方法,GeminiRegister再调用CallManager的注册方法,将客户端的信息封装到RegistrantList里面,使用时再调用RegistrantList的方法notifyRegistrants通知到其客户端。

如InCallScreen里面注册呼叫事件改变,

GeminiRegister.registerForPreciseCallStateChanged(callManager, mHandler, PHONE_STATE_CHANGED);

在CallManager里,mPreciseCallStateRegistrants是对应的RegistrantList,handler和事件被封装存放起来,

public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){

mPreciseCallStateRegistrants.addUnique(h, what, obj);

}

CallManager只是一个中间层,它不会自己产生状态信息,它的信息要来源于它的下层。所以它本身也会向下注册(后面会讲到)。如果他收到下层上报的EVENT_PRECISE_CALL_STATE_CHANGED事件,则在handler里面调用它的上层已经注册的RegistrantList,将状态改变通知到其客户端InCallScreen,

case EVENT_PRECISE_CALL_STATE_CHANGED:

case EVENT_PRECISE_CALL_STATE_CHANGED + NOTIFICATION_ID_OFFSET:

case EVENT_PRECISE_CALL_STATE_CHANGED + (NOTIFICATION_ID_OFFSET * 2):

case EVENT_PRECISE_CALL_STATE_CHANGED + (NOTIFICATION_ID_OFFSET * 3):

if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)");

index = (msg.what - EVENT_PRECISE_CALL_STATE_CHANGED) / NOTIFICATION_ID_OFFSET;

mPreciseCallStateRegistrantsGemini[index].notifyRegistrants((AsyncResult) msg.obj);

mPreciseCallStateRegistrants.notifyRegistrants((AsyncResult) msg.obj);

handle3GSwitchLock();

try {

checkIfExistsFollowingAction();

} catch (Exception e) {

//Do nothing.

}

break;

InCallScreen类主要负责通话界面的绘制和通话过程的控制,它也有一个handler,当CallManager通知呼叫状态改变时,它会处理自己注册的事件

case PHONE_STATE_CHANGED:

onPhoneStateChanged((AsyncResult) msg.obj);

2)向下注册获取服务

在registerPhone里,提供初始注册服务时,也调用了registerSinglePhoneStates向下注册服务,方法是调用phone提供的注册方法。例如        phone.registerForPreciseCallStateChanged(mHandler, EVENT_PRECISE_CALL_STATE_CHANGED + eventDifference*simId, null);就是用来向phone注册呼叫状态改变的。phone是GSMPhone的实例引用,registerForPreciseCallStateChanged是Phonebase的方法,GSMPhone继承自Phonebase。registerForPreciseCallStateChanged的实现是将CallManager的handler和EVENT_PRECISE_CALL_STATE_CHANGED封装起来,存放在在Phonebase的mPreciseCallStateRegistrants这个RegistrantList里面。

public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {

checkCorrectThread(h);

mPreciseCallStateRegistrants.addUnique(h, what, obj);

}

当呼叫状态改变时,需要将这个信息通知到上层应用,GSMPhone的notifyPreciseCallStateChanged会被GsmCallTracker.java调用,

phone.notifyPreciseCallStateChanged();

GSMPhone是继承自Phonebase的,所以最终是Phonebase的notifyPreciseCallStateChangedP被调用,由于之前注册过RegistrantList,所以使用RegistrantList的方法notifyRegistrants通知到其客户端CallManager。

protected void notifyPreciseCallStateChangedP() {

AsyncResult ar = new AsyncResult(null, this, null);

mPreciseCallStateRegistrants.notifyRegistrants(ar);

}

CallManager的handler会进行事件的处理。

1.2 CallNotifier

1.2.1 说明

CallNotifier

CallNotifier是一个Handler,它为PhoneApp处理各个主动上报来的一些消息。它监听来自Telephony层phone状态变化和其它各种事件,从而作出反应 如各种UI行为:启动铃音播放和来电显示UI、播放正在通话时的来电提示、更新状态栏提示(通过NotificationMgr)、通话记录添加等。

在PhoneBase中提供了一些RegistrantList,CallNotifier可以将自己作为一个感兴趣者注册进去,这样,当状态变化时,CallNotifier将得到通知,然后在线程中对其处理,作出UI方面的响应。在其构造函数中可以看出它处理的消息事件类别,下面的代码列出了部分要处理的消息种类(没有列出针对CDMA或GSM特定类型的消息):

mPhone.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);

mPhone.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);

mPhone.registerForDisconnect(this, PHONE_DISCONNECT, null);

mPhone.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);

mPhone.registerForIncomingRing(this, PHONE_INCOMING_RING, null);

当有PHONE_NEW_RINGING_CONNECTION类型消息到来时,意味着一个RINGING或WAITING的连接(connection)出现,此时handleMessage函数调用onNewRingingConnection来处理。后者先检查Settings里的设置是否可以接听电话;然后进行响铃(见InCallTonePlayer)和显示InCallScreen的UI,见PhoneUtils.showIncomingCallUi()和PhoneApp.displayCallScreen()两个函数。

通话过程中的铃音提示由线程类InCallTonePlayer完成。

当有PHONE_INCOMING_RING类型的消息到来时,意味着RIL层受到Ring,此处播放铃音。它使用的是Ringer.ring()函数,它会创建一个线程去播放铃音(见Ringer.makeLooper函数)。

当收到PHONE_STATE_CHANGED消息时,表明Phone的状态发生了改变,比如响铃后接通了电话,此时处理函数是onPhoneStateChanged,比如再次确认停止铃音、更新状态栏列的状态通知等。

当收到PHONE_DISCONNECT消息时,表明电话连接已挂断或RingCall断掉。其处理函数是onDisconnect。它清理现场诸如音频通道恢复、来电响铃的停止确认、对InCallScreen的UI清理、若有未接电话须在状态栏显示等。

1.3 PhoneInterfaceManager

1.3.1 说明

它继承自 Itelephony.Stub,实现了服务器侧的 ITelephony接口,其接口定义见文件:

frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl

TelephonyManager作为客户端通过Binder与其交互。

1.4 NotificationMgr

1.4.1 说明

NotificationMgr以静态成员函数的方式为PhoneApp用于Phone进程在状态栏中通知用户消息的功能,诸如:有未接电话、正在通话、是否静音等信息。它使用系统提供的API类NotificationManager和StatusBarManager完成通知功能。每项通知对应着通知、更新通知和取消通知的函数。当收到Message时,PhoneApp的Handler的handleMessage会使用NotificationMgr更新状态栏信息。下面的代码片段用于更新状态栏的的提示信息:

case EVENT_UPDATE_INCALL_NOTIFICATION:
NotificationMgr.getDefault().updateInCallNotification();//通话提示
break;
case EVENT_DATA_ROAMING_DISCONNECTED:
NotificationMgr.getDefault().showDataDisconnectedRoaming();//因漫游数据连接断开提示
break;
case EVENT_DATA_ROAMING_OK:
NotificationMgr.getDefault().hideDataDisconnectedRoaming();//隐藏漫游断开提示
break;

是否有未接电话的提示则是在PhoneApp创建NotificationMgr对象并调用其初始化函数时检查提示的。

1.5 InCallScreen

1.5.1 说明

它是手机正在通话时的Activity。当有来电、开始拨号或正在通话时,运行的是该Activity,UI界面是其对应的View:

// Inflate everything in incall_screen.xml and add it to the screen.
setContentView(R.layout.incall_screen);

在其OnCreate函数中将自己指定为PhoneApp的InCallScreen:

app.setInCallScreenInstance(this);

InCallScreen需要处理来电时跳过键盘锁直接可以接听电话、是否有耳机插入的情况、是否用蓝牙接听电话、需要监听并维护更新通话状态并显示给用户、需要支持通话过程中的某些功能(如发送DTMF、电话会议、分离一路通话)操作、OTA Call等。

CallCard是InCallScreen中的一个call(可能是当前的Call或保持的Call或来电Call)。

当需要接听电话或拨打电话时,上层发来intent,然后InCallScreen收到intent时它的InCallScreen.onNewIntent函数被调用,解析intent,要么调用placeCall拨打电话,要么调用internalAnswerCall接听电话。

应用程序可发出Intent进行电话呼叫,如在TwelveKeyDialer.java中进行呼叫时,去创建一个Intent:

void placeCall() {

final String number = mDigits.getText().toString();

boolean sendEmptyFlash = false;

Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,

Uri.fromParts(“tel”, number, null));

if (number == null || !TextUtils.isGraphic(number)) {

// There is no number entered.

if (phoneIsCdma() && phoneIsOffhook()) {

// We only want to send this empty flash extra if we’re CDMA and the

// phone is offhook (don’t want to send if ringing or dialing)

intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);

sendEmptyFlash = true;

} else {

playTone(ToneGenerator.TONE_PROP_NACK);

return;

}

}

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

startActivity(intent);

在phone程序中OutgoingCallBroadcaster(文件OutgoingCallBroadcaster.java)的包含一个内部类OutgoingCallReceiver,由它接收电话呼叫Intent,然后经过转换后再发送出去,由上述的InCallScreen接收处理,显示拨号界面并进行呼叫等。

如OutgoingCallBroadcaster所说,它接收 CALL 和CALL_PRIVILEGED 两种Intents,然后广播出ACTION_NEW_OUTGOING_CALL intent,让别的应用程序有机会去监视这些intent,最后这些呼叫intent又被自己收到转换,启动InCallScreen.

下面的代码是Intent转换,然后Intent被InCallScreen接收:

Intent newIntent = new Intent(Intent.ACTION_CALL, uri);

newIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);

PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent);

newIntent.setClass(context, InCallScreen.class);

newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

if (DBG) Log.v(TAG, “doReceive(): calling startActivity: ” + newIntent);

context.startActivity(newIntent);

}

这样做的目的是

InCallTouchUi:

通话过程中的按钮功能以及来电接听时的滑动接听功能。

ManageConferenceUtils:管理多方通话的工具,包括部分UI元素。借助PhoneUtils实现其功能。

DTMFTwelveKeyDialer:通话状态时的拨号盘,用于发送DTMF。

DTMFTwelveKeyDialerView:DTMF拨号视图布局类。

InCallControlState维护着一些状态信息,诸如是否Enable了Speaker声音免提、是否可以添加新的一路通话等等。它是MVC模式的数据部分。

InCallMenu是通话状态菜单,里面包含各个菜单项:

InCallMenuItemView mManageConference;
InCallMenuItemView mShowDialpad;
InCallMenuItemView mEndCall;
InCallMenuItemView mAddCall;
InCallMenuItemView mSwapCalls;
InCallMenuItemView mMergeCalls;
InCallMenuItemView mBluetooth;
InCallMenuItemView mSpeaker;
InCallMenuItemView mMute;
InCallMenuItemView mHold;
InCallMenuItemView mAnswerAndHold;
InCallMenuItemView mAnswerAndEnd;
InCallMenuItemView mAnswer;
InCallMenuItemView mIgnore;

InCallMenuItemView继承自TextView,代表着手机处在通话状态时的菜单项。每个菜单项保护一个文本,一个可选的“绿灯”,代表着打开/关闭;在文本上面的可以有可选的图标。其成员函数可以给它们赋值。当它们被点击后,在函数InCallScreen.handleOnscreenButtonClick或InCallScreen.onClick中得到调用。前者用于InCallTouchUi上的点击事件。

InCallScreen的成员函数

registerForPhoneStates:用于注册InCallScreen对哪些状态感兴趣。对于GSM手机,则注册了这些:

mPhone.registerForPreciseCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
mPhone.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
mPhone.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null); mPhone.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
mPhone.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);

当状态变化收到Message后,由mHandler来响应处理。

2. ICS Android 4.2 呼叫流程

2.1 呼叫请求

2.1.1 拨号请求(Contact)

我们从拨号盘开始分析呼叫流程。我们知道,输入一个号码,点击Call按钮,就开始触发了一个呼叫,然后将进行号码检查、判断,网络状态检查等工作,代码逐步向下调用,经过telephony、ril,再通过AT指令,让modem完成信道请求、连接和相应信令处理。

1)阶段一

在代码里,DialpadFragment的click将被执行,根据按钮id,执行拨号功能,并调用dialButtonPressed,

case R.id.dialButton: {

if (mNeedCheckSetting) {

// Retrieve the haptic feedback setting.

mHaptic.checkSystemSetting();

mNeedCheckSetting = false;

}

mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys

Profiler.trace(Profiler.DialpadFragmentEnterClick);

dialButtonPressed();

Profiler.trace(Profiler.DialpadFragmentLeaveClick);

return;

dialButtonPressed再调用dialButtonPressedInner,主要工作是创建一个intent,并调用doCallOptionHandle开始呼叫相关的处理。

protected void dialButtonPressedInner(String number, int type) {

final Intent intent = ContactsUtils.getCallIntent(number,

(getActivity() instanceof DialtactsActivity ?

((DialtactsActivity) getActivity()).getCallOrigin() : null), type);

mCallOptionHandler.doCallOptionHandle(intent);

mClearDigitsOnStop = true;

下面是函数调用栈验证分析过程:

DialpadFragment.dialButtonPressedInner(String, int) line: 2647

DialpadFragment.dialButtonPressed() line: 2634

DialpadFragment.onClick(View) line: 1531

一些重要的数据表现形式:

number "12344" (id=830022532488)   //拨出的号码

cachedString "tel:12344" (id=830020302720) //uri里面的号码表述

mAction "android.intent.action.CALL_PRIVILEGED" (id=830016739648) //intent的action

2)阶段二

mCallOptionHandler 是ContactsCallOptionHandler的引用,其方法doCallOptionHandle主要是获取ITelephony服务,并调用父类CallOptionHandler的方法doCallOptionHandle,这里有2个变量非常重要,mCallOptionHandlerList和mSuccessor。

public void doCallOptionHandle(Context activityContext, Context applicationContext, Intent intent,

CallOptionBaseHandler.ICallOptionResultHandle resultHandler,

CellConnMgr cellConnMgr, ITelephony telephonyInterface,

boolean isMultipleSim, boolean is3GSwitchSupport) {

ListIterator<CallOptionBaseHandler> iterator = mCallOptionHandlerList.listIterator();

CallOptionBaseHandler previousHandler = iterator.next();

while (iterator.hasNext()) {

CallOptionBaseHandler currentHandler = (CallOptionBaseHandler)iterator.next();

previousHandler.setSuccessor(currentHandler);

previousHandler = currentHandler;

}

Request request = new Request(activityContext, applicationContext, intent, resultHandler,

cellConnMgr, telephonyInterface, isMultipleSim, is3GSwitchSupport,

mCallOptionHandlerFactory);

mCallOptionHandlerList.getFirst().handleRequest(request);

}

mCallOptionHandlerList是CallOptionBaseHandler类型的LinkedList,它存放了9个呼叫处理类,每个MO呼叫要依次被这9个呼叫处理类处理,主要完成的功能是号码处理、不同呼叫类型的处理和网络状态相关的处理(详细流程可自行分析)。要实现这样的功能,上面的doCallOptionHandle做了一个循环,通过setSuccessor将下一个处理类作为上一个类的mSuccessor,这样在上一个处理类完成之后,调用mSuccessor就可以执行到下一个类,类似于函数指针。

public CallOptionHandler(CallOptionHandlerFactory callOptionHandlerFactory) {

mCallOptionHandlerFactory = callOptionHandlerFactory;

mCallOptionHandlerList = new LinkedList<CallOptionBaseHandler>();

mCallOptionHandlerList.add(callOptionHandlerFactory.getFirstCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getInternetCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getVideoCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getSimSelectionCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getSimStatusCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getVoiceMailCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getInternationalCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getIpCallOptionHandler());

mCallOptionHandlerList.add(callOptionHandlerFactory.getFinalCallOptionHandler());

}

第一个执行的类是ContactsFirstCallOptionHandler,之后通过下面的方式跳转到下一个处理类,

if (null != mSuccessor) {

mSuccessor.handleRequest(request);

}

再执行到ContactsSimStatusCallOptionHandler的时候,handleRequest根据条件是否需要检查sim状态,发送了一个消息MESSAGE_CHECK_SIM_STATUS,

public void handleRequest(final Request request) {

int slot = request.getIntent().getIntExtra(Constants.EXTRA_SLOT_ID, -1);

if (needToCheckSIMStatus(slot))

mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_CHECK_SIM_STATUS, slot, 0));

其自身的handleMessage处理了这个消息,处理网络状态(通过handleCellConn(),再使用服务IPhoneStatesMgrService的方法verifyPhoneState),并在这里结束了这一阶段的处理过程。可以看到我们的9个处理类还并没有执行完。

public void handleMessage(Message msg) {

switch (msg.what) {

case MESSAGE_CHECK_SIM_STATUS:

final int result = mRequest.getCellConnMgr().handleCellConn(msg.arg1,

CellConnMgr.REQUEST_TYPE_ROAMING, mRunnable);

log("result = " + result);

if (result == mRequest.getCellConnMgr().RESULT_WAIT) {

showProgressIndication(mRequest);

}

break;

下面是函数调用栈验证分析过程:

ContactsSimStatusCallOptionHandler(SimStatusCallOptionHandler).handleRequest(Request) line: 119

ContactsSimSelectionCallOptionHandler(SimSelectionCallOptionHandler).onMakeCall(SimSelectionCallOptionHandler$CallbackArgs) line: 432

ContactsSimSelectionCallOptionHandler(SimSelectionCallOptionHandler).handleRequest(Request) line: 279

ContactsVideoCallOptionHandler(VideoCallOptionHandler).handleRequest(Request) line: 64

ContactsInternetCallOptionHandler(InternetCallOptionHandler).handleRequest(Request) line: 64

ContactsFirstCallOptionHandler(FirstCallOptionHandler).handleRequest(Request) line: 73

ContactsFirstCallOptionHandler.handleRequest(Request) line: 58

ContactsCallOptionHandler(CallOptionHandler).doCallOptionHandle(Context, Context, Intent, CallOptionBaseHandler$ICallOptionResultHandle, CellConnMgr, ITelephony, boolean, boolean) line: 54

ContactsCallOptionHandler.doCallOptionHandle(Intent) line: 43

DialpadFragment.dialButtonPressedInner(String, int) line: 2670

DialpadFragment.dialButtonPressed() line: 2634

DialpadFragment.onClick(View) line: 1531

【Tips:】对于MTK平台,调试会有些文件找不到,如ContactsCallOptionHandler,可在.classpath中加入一条记录

<classpathentry kind="src" path="packages/apps/Phone/common/src"/>

3)阶段三

在这个阶段,我们需要找到拨号产生的intent如何向下传递的。

SimStatusCallOptionHandler的mRunnable将会被执行到,它处理了sim状态之后,又调用到mSuccessor。

private Runnable mRunnable = new Runnable() {

public void run() {

final int result = mRequest.getCellConnMgr().getResult();

final int slot = mRequest.getCellConnMgr().getPreferSlot();

log("run, result = " + result + " slot = " + slot);

dismissProgressIndication();

if (result != com.mediatek.CellConnService.CellConnMgr.RESULT_STATE_NORMAL) {

mRequest.getResultHandler().onHandlingFinish();

} else {

int oldSolt = mRequest.getIntent().getIntExtra(Constants.EXTRA_SLOT_ID, -1);

log("afterCheckSIMStatus, oldSolt = " + oldSolt);

if (oldSolt != -1 && slot != oldSolt) {

mRequest.getIntent().putExtra(Constants.EXTRA_SLOT_ID, slot);

}

//mRequest.getResultHandler().onContinueCallProcess(mRequest.getIntent());

if (null != mSuccessor) {

mSuccessor.handleRequest(mRequest);

}

}

}

};

这样又走回mCallOptionHandlerList所设计的串行迭代处理逻辑。后面将继续执行VoiceMailCallOptionHandler、InternationalCallOptionHandler、IpCallOptionHandler和FinalCallOptionHandler这几个类的判断处理过程。在FinalCallOptionHandler里面handleRequest结束了串行处理过程,

public void handleRequest(final Request request) {

log("handleRequest()");

request.getResultHandler().onContinueCallProcess(request.getIntent());

}

最后执行到ContactsCallOptionHandler的onContinueCallProcess,这里就通过sendBroadcast将intent广播出去了,并通过setClassName设置了接收类为com.mediatek.phone.OutgoingCallReceiver,到这里就完成了拨号盘拨号请求阶段的任务。

public void onContinueCallProcess(Intent intent) {

/** M: Ensure the Dialogs be dismissed before launch a new "call" @{ */

dismissDialogs();

/** @} */

intent.setAction(Constants.OUTGOING_CALL_RECEIVER);

intent.setClassName(Constants.PHONE_PACKAGE, Constants.OUTGOING_CALL_RECEIVER);

ContactsApplication.getInstance().sendBroadcast(intent);

}

下面是函数调用栈验证分析过程:

ContactsCallOptionHandler.onContinueCallProcess(Intent) line: 59

FinalCallOptionHandler.handleRequest(Request) line: 47

ContactsIpCallOptionHandler(IpCallOptionHandler).handleRequest(Request) line: 91

ContactsInternationalCallOptionHandler(InternationalCallOptionHandler).handleRequest(Request) line: 308

ContactsVoiceMailCallOptionHandler(VoiceMailCallOptionHandler).handleRequest(Request) line: 66

SimStatusCallOptionHandler$2.run() line: 94

2.1.2 拨号请求(Phone)

拨号盘里的拨号过程是在contact进程实现的,当它发出拨号广播后,就被Phone进程的com.mediatek.phone.OutgoingCallReceiver接收到,OutgoingCallReceiver的onReceive收到广播事件后,又发送一个ACTION_NEW_OUTGOING_CALL类型的广播,它自己能接收并处理这个广播,并调用callController.placeCall进行下一步的呼叫处理。

public void onReceive(Context context, Intent intent) {

if (Constants.OUTGOING_CALL_RECEIVER.equals(intent.getAction())) { //收到contact的广播

///Profiler.trace(Profiler.OutgoingCallReceiverEnterActionOnReceive);

Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);

//String number = PhoneNumberUtils.getNumberFromIntent(intent, context);

String number = CallOptionUtils.getInitialNumber(context, intent);

OutgoingCallBroadcaster.sendNewCallBroadcast(context, intent, number, false, this);//转发出去

} else if (Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())) {//自己接收

PhoneGlobals.getInstance().callController.placeCall(newIntent);//调用placeCall

}

callController的placeCall完成3部分的工作,1)检查intent的数据并调用placeCallInternal,向底层发起呼叫请求,2)处理底层的返回结果 3)启动通话界面

public void placeCall(Intent intent) {

CallStatusCode status = placeCallInternal(intent);

switch (status) {

// Call was placed successfully:

case SUCCESS:

case EXITED_ECM:

mApp.displayCallScreen(!intent.getBooleanExtra(Constants.EXTRA_IS_VIDEO_CALL, false), forPlaceCall);

}

placeCallInternal对intent和当前手机状态进行了一些检查,看是是紧急拨号、是否允许拨号等,在进一步向下调用PhoneUtils.placeCallGemini。

callStatus = PhoneUtils.placeCallGemini(mApp,

phone,

number,

contactUri,

(isEmergencyNumber || isEmergencyIntent),

inCallUiState.providerGatewayUri,

slot);

placeCallGemini则进一步调用GeminiRegister.dial,GeminiRegister.dial 调用((MTKCallManager) callManager).dialGemini,其中MTKCallManager是MTK的封装类,最终会调用的CallManager的dial,在CallManager.dial中调用了basePhone.dial(dialString),basePhone实际上是一个GSMPhone的实例,所以最终调用下面的方法。其中mCT则是GsmCallTracker的实例,它在GSMPhone创建的时候被创建,GSMPhone通过它来完成呼叫相关的处理。

public Connection

dial (String dialString, UUSInfo uusInfo) throws CallStateException {

GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this, mUiccApplication.get());

if (LOCAL_DEBUG) Cclog("dialing w/ mmi '" + mmi + "'...");

//MTK-END [mtk04070][111118][ALPS00093395]Add Cclog

if (mmi == null) {

return mCT.dial(newDialString, uusInfo);

} else if (mmi.isTemporaryModeCLIR()) {

return mCT.dial(mmi.dialingNumber, mmi.getCLIRMode(), uusInfo);

} else {

mPendingMMIs.add(mmi);

mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));

mmi.processCode();

// FIXME should this return null or something else?

return null;

}

}

在GsmCallTracker的dial方法中,会先将音频通道mute,再进行cm.dial拨号,之后再将状态信息更新到应用。其中cm是RIL.java的实例。这里还封装了一个事件为EVENT_DIAL_CALL_RESULT消息,但RIL层响应拨号请求后,被自身的handler处理。(clearDisconnected()和canDial()清空过去的非连接状态的Connections,然后检查是否可以拨打电话。接着检查foregroundCall是否处于Active状态,若是则调用switchWaitingOrHoldingAndActive将它们切换到后台,调用fakeHoldForegroundBeforeDial将前台中的连接全部切换到后台,并且状态变为HOLDING。在进行这些前期检查和准备后,创建一个GsmConnection实例即pendingMO,检查传递过来的电话号码是否有效合法,若不合法则调用pollCallsWhenSafe(),目的是将其标为dropped;若合法则设置为非静音后,调用RIL.dial进行拨号。最后,更新Phone状态并通知给注册者。)

synchronized Connection

dial (String dialString, int clirMode, UUSInfo uusInfo) throws CallStateException {

clearDisconnected();

setMute(false);

if (PhoneNumberUtils.isEmergencyNumber(dialString)

cm.emergencyDial(ret.toString(), clirMode, uusInfo, obtainCompleteMessage(EVENT_DIAL_CALL_RESULT));

} else {

cm.dial(ret.toString(), clirMode, uusInfo, obtainCompleteMessage(EVENT_DIAL_CALL_RESULT));

}

updatePhoneState();

phone.notifyPreciseCallStateChanged();

}

RIL.dial实现如下,封装一个RIL_REQUEST_DIAL类型的消息发送出去,将拨号请求发送给RILD,之后RILD再使用AT指令向modem发送呼叫请求。对于RILD之后的处理我们暂不进行分析了。(AT及modem这部分是feature phone很常规的呼叫流程过程,不属于android的特色代码。)

public void

dial(String address, int clirMode, UUSInfo uusInfo, Message result) {

RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);

rr.mp.writeString(address);

rr.mp.writeInt(clirMode);

rr.mp.writeInt(0); // UUS information is absent

if (uusInfo == null) {

rr.mp.writeInt(0); // UUS information is absent

} else {

rr.mp.writeInt(1); // UUS information is present

rr.mp.writeInt(uusInfo.getType());

rr.mp.writeInt(uusInfo.getDcs());

rr.mp.writeByteArray(uusInfo.getUserData());

}

send(rr);

}

这个拨号过程的调用栈如下,

下面是函数调用栈验证分析过程:

RIL.dial(String, int, UUSInfo, Message) line: 1148

GsmCallTracker.dial(String, int, UUSInfo) line: 420

GsmCallTracker.dial(String, UUSInfo) line: 443

GSMPhone.dial(String, UUSInfo) line: 1007

GSMPhone.dial(String) line: 999

CallManager.dial(Phone, String) line: 1382

MTKCallManager.dialGemini(Phone, String, int) line: 147

GeminiRegister.dial(Object, Phone, String, int) line: 964

PhoneUtils.placeCallGemini(Context, Phone, String, Uri, boolean, Uri, int) line: 859

CallController.placeCallInternal(Intent) line: 746

CallController.placeCall(Intent) line: 349

OutgoingCallReceiver.onReceive(Context, Intent) line: 72

LoadedApk$ReceiverDispatcher$Args.run() line: 788

RIL响应后,会调用processResponse,再调用processSolicited,processSolicited对RIL_REQUEST_DIAL处理比较简单,没有实质性的代码。在最后的代码里面,如下,则对之前封装的EVENT_DIAL_CALL_RESULT消息进行处理,进行回调状态处理。

if (rr.mResult != null) {

AsyncResult.forMessage(rr.mResult, ret, null);

rr.mResult.sendToTarget();

}

这个回调处理实际由GsmCallTracker的handleMessage完成。

EVENT_DIAL_CALL_RESULT执行的调用栈,

GsmCallTracker.handleMessage(Message) line: 1271

GsmCallTracker(Handler).dispatchMessage(Message) line: 107

2.1.3 进入通话界面

由于之前提到的callController的placeCall里启动了呼叫界面,拨号请求完成后,会进入inCallScreen,onCreate会被执行,开始构建通话界面。

在拨号请求后,updatePhoneState会被调用到,最初处理的是phone state由IDLE到OFF HOOK的变化,updatePhoneState的主要作用是更新Phone的呼叫状态,并将状态通知。

private void    updatePhoneState() {

PhoneConstants.State oldState = state;

if (ringingCall.isRinging()) {

state = PhoneConstants.State.RINGING;

} else if (pendingMO != null ||

!(foregroundCall.isIdle() && backgroundCall.isIdle())) {

state = PhoneConstants.State.OFFHOOK;

} else {

state = PhoneConstants.State.IDLE;

}

if (state == PhoneConstants.State.IDLE && oldState != state) {

voiceCallEndedRegistrants.notifyRegistrants(

new AsyncResult(null, null, null));

} else if (oldState == PhoneConstants.State.IDLE && oldState != state) {

voiceCallStartedRegistrants.notifyRegistrants (

new AsyncResult(null, null, null));

}

if (state != oldState) {

phone.notifyPhoneStateChanged();

}

}

代码里还有两种特殊情况,

如果新状态变为IDLE,则表示通话断开,要发给voiceCallEndedRegistrants里面的注册函数处理,在GsmDataConnectionTracker里通过

p.getCallTracker().registerForVoiceCallEnded (this, DctConstants.EVENT_VOICE_CALL_ENDED, null); 来注册。

如果上一个状态是IDLE,则表示新建立通话,要发给voiceCallStartedRegistrants里面的注册函数处理,在GsmDataConnectionTracker里通过p.getCallTracker().registerForVoiceCallStarted (this, DctConstants.EVENT_VOICE_CALL_STARTED, null);

对于EVENT_VOICE_CALL_ENDED和.EVENT_VOICE_CALL_STARTED 的处理,先通过GsmDataConnectionTracker的handlemessage处理,没有匹配的case,再转交给父类 ,父类调用onVoiceCallEnded和onVoiceCallStarted方法进行处理,这两个方法在父类是抽象函数,实际又调用到了子类的函数。

onVoiceCallStarted

protected void onVoiceCallStarted() {

if (DBG) log("onVoiceCallStarted");

if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {

notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);

}

if (isConnected() && ! mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {

if (DBG) log("onVoiceCallStarted stop polling");

stopNetStatPoll();

stopDataStallAlarm();

}

}

onVoiceCallEnded

protected void onVoiceCallEnded() {

if (DBG) log("onVoiceCallEnded");

if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {

notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);

}

if (isConnected()) {

if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {

startNetStatPoll();

startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);

} else {

// clean slate after call end.

resetPollStats();

}

}

// reset reconnect timer

setupDataOnReadyApns(Phone.REASON_VOICE_CALL_ENDED);

}

我们重点关注notifyPhoneStateChanged是怎样将信息传递到应用上层的,onVoiceCallEnded和onVoiceCallStarted的过程类似。(使用SIM1拨号,观察log居然发现sim2收到了EVENT_VOICE_CALL_STARTED_PEER事件并进行了处理,待分析。)

1)

notifyPhoneStateChanged首先通过DefaultPhoneNotifier的notifyPhoneState,将信息传递给ITelephonyRegistry的notifyCallState,再通过AIDL,将消息传递给TelephonyRegistry的notifyCallState,这里会进行注册函数的遍历,并将状态通知到每个注册的函数,之后还会进行广播。

public void notifyCallState(int state, String incomingNumber) {

if (!checkNotifyPermission("notifyCallState()")) {

return;

}

synchronized (mRecords) {

mCallState = state;

mCallIncomingNumber = incomingNumber;

for (Record r : mRecords) {

if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {

try {

r.callback.onCallStateChanged(state, incomingNumber); //遍历

} catch (RemoteException ex) {

mRemoveList.add(r.binder);

}

}

}

handleRemoveListLocked();

}

broadcastCallStateChanged(state, incomingNumber); //广播

}

2)

TelephonyRegistry里遍历的回调函数onCallStateChanged会将消息发给IPhoneStateListener,通过AIDL方法,由PhoneStateListener接收并处理。

3)

在应用最上层,DialpadFragment.java通过SlotUtils.listenAllSlots间接向TelephonyRegistry注册,

SlotUtils.listenAllSlots(getActivity(), mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE

| PhoneStateListener.LISTEN_SERVICE_STATE);

并且,DialpadFragment.java创建PhoneStateListener的实例,并实现了onCallStateChanged方法。

public void onCallStateChanged(int state, String incomingNumber) {

if (state == TelephonyManager.CALL_STATE_IDLE) {

final boolean phoneIsInUse = phoneIsInUse();

if (dialpadChooserVisible()) {

if (!phoneIsInUse) {

showDialpadChooser(false);

adjustListViewLayoutParameters();

}

}

//modified by yanbin.zhang for fr441210 begin 2013-06-11

if (!phoneIsInUse && !simIsnotInUse()) {

//modified by yanbin.zhang for fr441210 begin 2013-06-11

if (mDigits != null) {

mDigits.setHint(null);

}

}

}

}

在PhoneStateListener的handleMessage处理LISTEN_CALL_STATE时,通过如下代码,调用DialpadFragment.java里的onCallStateChanged方法。

case LISTEN_CALL_STATE:

PhoneStateListener.this.onCallStateChanged(msg.arg1, (String)msg.obj);

break;

这样就完成了Phone state的向上传递。

2.2 呼叫建立

2.2.1 ECPI

RILJ收到呼叫请求并发出相应后,随即收到RILC的事件并进行处理,如下,

case RIL_UNSOL_CALL_PROGRESS_INFO:

if (RILJ_LOGD) unsljLogvRet(response, ret);

if (mCallProgressInfoRegistrants != null) {

mCallProgressInfoRegistrants.notifyRegistrants(

new AsyncResult (null, ret, null));

其中mCallProgressInfoRegistrants里面的处理实体是在GsmCallTracker注册完成的,注册函数为cm.registerForCallProgressInfo(this, EVENT_CALL_PROGRESS_INFO, null);,里面生成一个Registrants并添加到mCallProgressInfoRegistrants:mCallProgressInfoRegistrants.add()。

这样,EVENT_CALL_PROGRESS_INFO消息就会被传递给GsmCallTracker.java的handleMessage来处理,事件的处理过程在handleCallProgressInfo里面。

EVENT_CALL_PROGRESS_INFO携带的消息格式和说明如下,在handleCallProgressInfo有说明。

/* +ECPI:<call_id>, <msg_type>, <is_ibt>, <is_tch>, <dir>, <call_mode>[, <number>, <toa>], "",<cause>

*

* if msg_type = DISCONNECT_MSG or ALL_CALLS_DISC_MSG,

* +ECPI:<call_id>, <msg_type>, <is_ibt>, <is_tch>,,,"",,"",<cause>

*

* if msg_type = STATE_CHANGE_HELD or STATE_CHANGE_ACTIVE or STATE_CHANGE_DISCONNECTED,

* +ECPI:<call_id>, <msg_type>,,,,,"",,""

*

* if others,

* +ECPI:<call_id>, <msg_type>, <is_ibt>, <is_tch>, <dir>, <call_mode>[, <number>, <toa>], ""

* msg_type:

*     0  O  CSMCC_SETUP_MSG   呼叫建立,用于MT

*     1  X  CSMCC_DISCONNECT_MSG 呼叫断开

*     2  O  CSMCC_ALERT_MSG  等待接通提示

*     3  X  CSMCC_CALL_PROCESS_MSG 呼叫处理

*     4  X  CSMCC_SYNC_MSG  信息同步

*     5  X  CSMCC_PROGRESS_MSG  呼叫处理

*     6  O  CSMCC_CALL_CONNECTED_MSG  呼叫连接

*   129  X  CSMCC_ALL_CALLS_DISC_MSG  断开所有呼叫

*   130  O  CSMCC_MO_CALL_ID_ASSIGN_MSG  CALL ID指定

*   131  O  CSMCC_STATE_CHANGE_HELD   呼叫保持状态

*   132  O  CSMCC_STATE_CHANGE_ACTIVE  呼叫激活状态

*   133  O  CSMCC_STATE_CHANGE_DISCONNECTED  呼叫断开

*   134  X  CSMCC_STATE_CHANGE_MO_DISCONNECTING  MO主动断开呼叫

*/

handleCallProgressInfo,从名字可以看出,它负责处理呼叫处理信息,分析其代码,其处理的呼叫分MO、MT两大类,涉及的过程和知识点有呼叫建立和维护的各种状态,处理语音和视频呼叫、多方通话等功能。

上面是一个MO呼叫的AT数据,从LOG上可以看到,ATD拨号命令发出后,收到很多ECPI数据,ECPI含义是呼叫处理信息(Call Process information)。可以看到MO呼叫建立时,会依次收到msg_type为130,3,4,2,132,6的CPI信息,从前面的描述可以看到,MO呼叫建立过程为,

130  CALL ID指定

3      CSMCC_CALL_PROCESS_MSG 呼叫处理

4    CSMCC_SYNC_MSG  信息同步

2    CSMCC_ALERT_MSG  等待接通提示

132    CSMCC_STATE_CHANGE_ACTIVE  呼叫激活状态

6    CSMCC_CALL_CONNECTED_MSG  呼叫连接

1.1.1 ECPI消息处理

1)

先分析msg_type为130的处理过程,数据如下:

+ECPI:<call_id>, <msg_type>, <is_ibt>, <is_tch>, <dir>, <call_mode>[, <number>, <toa>], "",<cause>

+ECPI: 1,     130,       0,       0    ,0,    0,      "10086",129,""

由于代码量较大,我们只列出LOG表示handleCallProgressInfo代码的执行流程,这需要对照代码分析。LOG分两部分说明,

第一部分:

可以看出conn == null, pendingMO != null,Phone State=OFFHOOK,Foreground call,Background call,这些变量对我们分析呼叫的状态十分重要,我们将在其他地方展开详述。

期间调用的处理函数主要为

第二部分:

可以看出newRinging = null,droppedDuringPoll.size=0,hasNonHangupStateChanged=true。

期间调用的处理函数主要为internalClearDisconnected,updatePhoneState,phone.notifyPreciseCallStateChanged。

internalClearDisconnected主要完成清除ringingCall、foregroundCall、backgroundCall里已挂断电话的connection。这几个呼叫实例是GsmCall类,继承自Call,维护call connection的状态。通过attach将呼叫连接添加到ArrayList里面,再在呼叫过程中,对这些这些连接进行维护。

private void    internalClearDisconnected() {

ringingCall.clearDisconnected();

foregroundCall.clearDisconnected();

backgroundCall.clearDisconnected();

}

updatePhoneState的流程分析见上文。

notifyPreciseCallStateChanged将state通知到注册到的应用,应用注册是通过PhoneBase 的registerForPreciseCallStateChanged完成的。

对于呼叫部分,在CallManager里的registerForPhoneStates,registerSinglePhoneStates里面进行注册。CallManager再在handler里面处理EVENT_PRECISE_CALL_STATE_CHANGED消息,并将消息通知给mPreciseCallStateRegistrantsGemini和mPreciseCallStateRegistrants

的注册客户。

mPreciseCallStateRegistrantsGemini里存放的实例是通过registerForPreciseCallStateChangedEx注册的,经查找没有发现有客户注册。

mPreciseCallStateRegistrants里存放的实例是通过registerForPreciseCallStateChanged来注册,对于呼叫部分,是InCallScreen.java来注册的。InCallScreen的handleMessage处理PHONE_STATE_CHANGED事件,调用onPhoneStateChanged,更新通话界面和相关状态。

2)

再分析msg_type为2的处理过程,数据如下:

+ECPI:<call_id>, <msg_type>, <is_ibt>, <is_tch>, <dir>, <call_mode>[, <number>, <toa>], "",<cause>

ECPI: 1,        2,        1,      1,     0,     0,     "10086",129,""

msg_type为2,State变为ALERTING,会通知到应用,这个状态通知上去之后,界面会变化为等待对方接听的界面(影响界面变化的应该为notifyPreciseCallStateChanged)。

期间调用的处理函数主要为internalClearDisconnected,updatePhoneState,phone.notifyPreciseCallStateChanged。

3)

对于其他类型的消息分析过程类似,不详细说明。

1.1.1 界面更新

notifyPreciseCallStateChanged将phone state通知到InCallScreen.java,InCallScreen的handleMessage处理PHONE_STATE_CHANGED事件,调用onPhoneStateChanged,onPhoneStateChanged再调用requestUpdateScreen来更新界面。而onPhoneStateChanged则给mHandler发送REQUEST_UPDATE_SCREEN消息,让mHandler再调用updateScreen来刷新屏幕,

case REQUEST_UPDATE_SCREEN:

case REQUEST_UPDATE_SCREEN_EXT:

updateScreen();

break;

updateScreen负责界面的刷新。主要的刷新函数是mCallCard.updateState(mCM)。

1.1 呼叫结束

1.1.1 ECPI消息处理

呼叫结束的AT数据如下。

可以看出,需要处理的CPI信息依次如下,

*   134  X  CSMCC_STATE_CHANGE_MO_DISCONNECTING  MO主动断开呼叫

*    1  X  CSMCC_DISCONNECT_MSG 呼叫断开

*   129  X  CSMCC_ALL_CALLS_DISC_MSG  断开所有呼叫

*   133  O  CSMCC_STATE_CHANGE_DISCONNECTED  呼叫断开

对象这些信息的代码分析就不再赘述了。

133的处理log:

updatePhoneState的时候会调用onVoiceCallEnded。并且sim2会收到一个EVENT_VOICE_CALL_ENDED_PEER事件,并执行onVoiceCallEndedPeer。

1.1.1 界面更新

断开连接时,通过onDisconnect来更新界面,调用栈如下:

InCallScreen.updateScreen(boolean) line: 4033

InCallScreen.onDisconnect(AsyncResult, int) line: 3527

InCallScreen.access$400(InCallScreen, AsyncResult, int) line: 178

InCallScreen$1.handleMessage(Message) line: 523

InCallScreen$1(Handler).dispatchMessage(Message) line: 107

1.1 MO呼叫流程图

根据以上分析,一个MO呼叫并主动断开的流程图如下:

呼叫请求

呼叫建立:

呼叫主动断开:

1.1 MT呼叫

1.1.1 接听

ringingCall正处于INCOMING则调用RIL.acceptCall去接听电话;若是WAITING状态,则调用switchWaitingOrHoldingAndActive将其切换到前台。

1.1.2 拒接

当ringingCall处于INCOMING时,则调用RIL.rejectCall拒绝;否则抛出异常,表示没有来电却去接听它。

1.2 挂断呼叫

它区分是ringingCall、foregroundCall还是backgroundCall。若是ringingCall,则调用RIL.hangupWaitingOrBackground;若是foregroundCall,并且是在DIALING或ALERTING状态则调用调用hangup (GsmConnection conn)挂断,否则调用hangupForegroundResumeBackground挂断前台通话后恢复后台通话;若是backgroundCall且ringingCall在响铃,则调用hangupAllConnections挂断所有在backgroundCall的通话连接,否则调用hangupWaitingOrBackground挂断呼叫等待和后台通话。

switchWaitingOrHoldingAndActive():进行电话切换
conference():进行电话会议
separate():分离出一路通话
fakeHoldForegroundBeforeDial()将前台电话(ForegroundCall)中的电话连接(GSMConnections)clone后台后,将这些连接删除,将Foreground置为IDLE状态,将后台电话BackgroundCall置为HOLDING状态。
clearDisconnected():清除已Disconnected的电话连接并更新电话状态,然后通知注册者当前最新的状态。
internalClearDisconnected():将ringingCall、 foregroundCall和 backgroundCall中状态为DISCONNECTED的 connections清除掉,若没有connection则将该GSMCall置为idle状态。
updatePhoneState():更新Phone状态,并向注册者通知语音通话开始或结束。
canDial():检查是否可以拨打电话,只有同时满足下列条件才可以:(1)射频Raido未关闭(2)PendingMO这个Connection为空(3)RingCall这个GSMCall未处于响铃状态(4)系统没有禁止电话拨打功能(5)ForegroundCall和BackgroundCall这2个GSMCall都不处于活动状态。其中当为INCOMING 和WAITING表示在响铃;当为DIALING 和 ALERTING时 表示在拨号;当不为IDLE 、 DISCONNECTED 和DISCONNECTING时表示活动状态,即处在上述的响铃、拨号、ACTIVE 和HOLDING时表示处于活动状态。
canConference():当ForegroundCall处于Active、BackgroundCall处于HOLDING以及它们的连接数少于5个时则可进行电话会议
canTransfer():当ForegroundCall处于Active、BackgroundCall处于HOLDING时则可进行交换。
hangup (GsmConnection conn):挂断某路电话连接
hangupWaitingOrBackground():挂断呼叫等待和后台电话
hangupForegroundResumeBackground():挂断前台电话并恢复后台电话
hangupConnectionByIndex(GsmCall call, int index):挂断某个索引指定的前台通话
hangupAllConnections(GsmCall call):挂断所有电话通路

6572 Phone call分析相关推荐

  1. Python3.5源码分析-内存管理

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的内存管理概述 python提供了对内存的垃圾收 ...

  2. 宏基因组、微生物、环境杂志影响因子(IF)及变化分析

    转发朋友圈,截图发送至公众号后台,获取全部杂志,全部!13010本杂志的影响因子! 以下是按类筛选的杂志,包括 宏基因组.微生物.生物技术和应用微生物.环境科学各分领域的杂志精选,以及变化分析 宏基因 ...

  3. 双轴机械臂建模分析数据

    作者:卓晴博士,清华大学自动化系 更新时间:2020-08-19 Wednesday ■ 背景 在 对于实验室双轴机械臂进行运动调试过程中,设计最后的控制器需要对运动对象进行力学建模分析.利用肩关节. ...

  4. FTP协议的分析和扩展

    2019独角兽企业重金招聘Python工程师标准>>> FTP协议的分析和扩展 出处: http://elly.blogdriver.com/index.jsp >> 1 ...

  5. php修改特定位bit的值,解读天书 - 漏洞利用中级技巧的分析

    题记: 距离上次更新感觉已经过了很久很久的时间,什么事情多时间少都是借口,自己变的懒了倒是真的,给大家道歉,以后更新会加快的,今天不讲漏洞分析,跟我来讨论下漏洞利用中的一些原理上的分析.本篇文章遵循思 ...

  6. 测试过程中如何分析抓包工具抓的HTTP或TCP包

    http://www.docin.com/p-101479451.html 工作中遇到C/S的通讯接口测试,经常会遇到由于请求的结构不对或者包发送错误,导致很多问题,通常需要通过抓包工具,把发送的包抓 ...

  7. java 字节码分析_Java 字节码实践 - 解读

    最近刚看完 深入理解 Java 虚拟机 一书中的第 6 章 (类文件结构),便迫不及待地自己写一个小的 Demo,来自己分析一把 Java 源文件经过编译之后成为字节码文件到底是个什么东西?先由一个简 ...

  8. python药店销售数据分析_药房销售情况分析(python篇)

    运用python中的numpy.pandas等包,可以帮助我们很方便的进行数据统计.数据分析.今天,通过朝阳医院2018的销售数据这个案例来简单做一下展示. 数据分析的基本过程一般分为以下几个部分: ...

  9. JDBC访问KingbaseES数据库异常 -- 案例分析

    应用使用jdbc访问KingbaseES数据库发生异常:SocketTimeoutException Read timed out 一.异常现象: 应用显示ERROR信息: Caused By: ja ...

  10. 关于谷歌浏览器主页被篡改无法修正的样例分析与解决方案

    关于谷歌浏览器主页被篡改无法修正的样例分析与解决方案 首先写在前面,浏览器被修改主页的情况很多,此次分析的仅为一个样例且给出解决的方法.本文主要提供自身经历和解决的思路,因为水平有限,无法给出专业化的 ...

最新文章

  1. 【Qt】Qt中使用ssl时报错:qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
  2. 【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记20 Multiple MVCs 多MVC模式、NavigationController导航控制器...
  3. 教你如何搭建虚拟专用网连接 OPEN***
  4. 计算机视觉——利用openCV与Socket结合进行远程摄像头实时视频传输并保存图片数据
  5. boost::johnson_all_pairs_shortest_paths用法的测试程序
  6. Java 流程控制与数组
  7. 通信原理包络是什么意思_罗茨鼓风机的应用及原理
  8. And Then There Was One POJ - 3517(变形约瑟夫环+规律)
  9. HTML + CSS 实现 GitHub 项目标签、徽章样式
  10. 在SQL Server中为什么不建议使用Not In子查询
  11. istio入门与实战 pdf 下载_Istio入门与实战
  12. tensorflow没有代码提示的问题
  13. 修改centos7的MAC地址
  14. 软件测试基础知识 + 面试理论(超详细)
  15. 科学计算机带度分秒,科学计算器度分秒
  16. arcgis投影坐标转经纬度
  17. 万年历查询,一个wonderful的年历
  18. “五子登科”新解之“票子,房子,车子,妻子,孩子”
  19. Android 选择图片、上传图片之Matisse
  20. ur机器人编程-坐标系

热门文章

  1. AQS之ReenReadWriteLock
  2. 使用MySQL进行地理坐标计算
  3. 我的世界java版粘土服务器ip,clay黏土服务器下载
  4. Matlab 常用快捷键
  5. R语言入门——一文讲明白attach与detach
  6. 管理咨询公司全球前22排名
  7. WordPress评论摘要标签:comment_excerpt
  8. ptp4l linux,如何使用PTP4l测试PTPV2协议精度?
  9. 计算机组成原理——计算机的运算方法
  10. Excel怎么转PDF格式?这些方法值得收藏