Android源码分析:Telephony部分–GSMPhone

PhoneProxy/GSMPhone/CDMAPhone

如果说RILJ提供了工具或管道,那么Phone接口的子类及PhoneFactory则为packages/app/Phone这个应用程序进程使用RILJ这个工具或管道提供了极大的方便,它们一个管理整个整个手机的Telephony功能。

GSMPhone和CDMAPhone实现了Phone中定义的接口。接口类Phone定义了一套API,这套API用于使用RILJ(见后述RIL类)发送AT命令请求,也还有一套register和unregister函数;当调用者对一些内部状态感兴趣时,可以调用对应的register函数,当状态变化时可以得到及时通知。

PhoneBase实现了Phone接口中定义的部分函数,还有一部分由其子类GSMPhone和CDMAPhone实现。PhoneProxy是GSMPhone和CDMAPhone的代理,让使用者不用关注手机到底是GSM还是CDMA,它遵守Phone定义的API接口,因此继承Phone。

PhoneFactory在创建Phone对象时,拥有的是PhoneProxy对象,PhoneProxy根据实际的网络类型创建对应的GSMPhone或CDMAPhone。

PhoneFactory同样拥有CommandInterface的接口对象,即RIL的实例,该RIL实例将被传递给GSMPhone或CDMAPhone,即GSMPhone或CDMAPhone引用它,实现与rild的交互。

GSMPhone和CDMAPhone继承自PhoneBase,它们(包括PhoneProxy)都是一个Handler。这样,它们就可以在线程的循环Looper.loop中处理来自RILJ或自身发送的各种Message(只要Message中指定了目的handler)。

PhoneFactory负责创建各个Phone实例,其成员及成员函数为static,可以保证创建的实例在系统运行时的唯一性。

 

 

上图是PhoneFactory拥有着PhoneProxy对象(实际代理的GSMPhone或CDMAPhone的功能)、RIL对象、PhoneNotofier对象和Looper对象,Context对象由上层的应用程序Java App传递过来,即某个Activity 。Android的应用程序可以使用PhoneFactory.getDefaultPhone来获得Phone对象,从而进行一些调用操作。

 

在PhoneFactory创建某种Phone时,创建的PhoneNotifier和RIL等实例的代码片段如下:

sPhoneNotifier = new DefaultPhoneNotifier();

//获取网络类型
//Get preferredNetworkMode from Settings.System
int networkMode = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.PREFERRED_NETWORK_MODE, preferredNetworkMode);

Log.i(LOG_TAG, “Network Mode set to ” + Integer.toString(networkMode));

//Get preferredNetworkMode from Settings.System
int cdmaSubscription = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.PREFERRED_CDMA_SUBSCRIPTION, preferredCdmaSubscription);

Log.i(LOG_TAG, “Cdma Subscription set to ” + Integer.toString(cdmaSubscription));

//reads the system properties and makes commandsinterface
sCommandsInterface = new RIL(context, networkMode, cdmaSubscription);//创建RIL对象

//根据网络类型获取手机Phone类型GSM还是CDMA
int phoneType = getPhoneType(networkMode);

//PhoneProxy对象,它根据手机类型代理不同的Phone的对象
if (phoneType == RILConstants.GSM_PHONE) {
sProxyPhone = new PhoneProxy(new GSMPhone(context,
sCommandsInterface, sPhoneNotifier));
Log.i(LOG_TAG, “Creating GSMPhone”);
} else if (phoneType == RILConstants.CDMA_PHONE) {
sProxyPhone = new PhoneProxy(new CDMAPhone(context,
sCommandsInterface, sPhoneNotifier));
Log.i(LOG_TAG, “Creating CDMAPhone”);
}

在GSMPhone和CDMAPhone中,创建与电话功能相关的各个模块,下面的代码片段来自GSMPhone的构造函数:

前面的部分代码用于创建各种对象,比如GsmCallTracker 对象mCT ;后面的register函数用于注 册GSMPhone对某种状态感兴趣,当状态变化时会得到通知,由handler(即自身this)去处理,GSMPPhone本身就是一个handler,可以处理这些状态变化时发送过来的Message。

mCM.setPhoneType(Phone.PHONE_TYPE_GSM);

mCT = new GsmCallTracker(this);
mSST = new GsmServiceStateTracker (this);
mSMS = new GsmSMSDispatcher(this);
mIccFileHandler = new SIMFileHandler(this);
mSIMRecords = new SIMRecords(this);
mDataConnection = new GsmDataConnectionTracker (this);
mSimCard = new SimCard(this);
if (!unitTestMode) {
mSimPhoneBookIntManager = new
SimPhoneBookInterfaceManager(this);
mSimSmsIntManager = new SimSmsInterfaceManager(this);
mSubInfo = new PhoneSubInfo(this);
}
mStkService = StkService.getInstance(mCM, mSIMRecords, mContext,
(SIMFileHandler)mIccFileHandler, mSimCard);

mCM.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
mSIMRecords.registerForRecordsLoaded(this,
EVENT_SIM_RECORDS_LOADED, null);
mCM.registerForOffOrNotAvailable(this,
EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
mCM.registerForOn(this, EVENT_RADIO_ON, null);
mCM.setOnUSSD(this, EVENT_USSD, null);
mCM.setOnSuppServiceNotification(this, EVENT_SSN, null);
mSST.registerForNetworkAttach(this, EVENT_REGISTERED_TO_NETWORK, null);

 

下图揭示了GSMPhone和CDMAPhone拥有的类实例,以及这些实例的类的继承关系。图的上半部分几个类本身都是handler,因此可以处理各种线程队列上的Message,见它们的handlerMessage函数。下半部分的几个类继承自XXX.stub,它是服务端的具体实现。调用者如Java APP(客户端)通过AIDL接口最终会调用到它们实现的函数。

 

 

代码分析示例–GSMCallTracker

GSMCallTracker实现了电话的拨打(Dial)、接听/拒绝(accept/reject)、挂断(hangup)、保持(hold)、切换以及电话会议等功能,它还负责查询Modem当前有多少路通话,维护电话状态等功能。

GSMCallTracker中包含了GsmConnection、RegistrantList、 GSMCall和Phone.State等类的对象实例。

GSMCall包含可以支持多路通话,每路通话意味着一个通话连接GsmConnection(最多5个)当它们状态均为DISCONNECTED时意外着该GSMCall为IDLE状态。

在GSMCallTracker构造函数中向RIL类实例注册了RegistrantList,当通话状态及射频Radio状态变化时,就会通知GSMCallTracker:

GsmCallTracker (GSMPhone phone) {
this.phone = phone;
cm = phone.mCM;

cm.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
cm.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
cm.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);

而GSMCallTracker的handleMessage就会做出相应处理:

case EVENT_CALL_STATE_CHANGE:
pollCallsWhenSafe();
break;
case EVENT_RADIO_AVAILABLE:
handleRadioAvailable();
break;
case EVENT_RADIO_NOT_AVAILABLE:
handleRadioNotAvailable();

它们最终都调用的是pollCallsWhenSafe去查询当前的通话状态。

dial:拨打电话。它首先clearDisconnected()和canDial()清空过去的非连接状态的Connections,然后检查是否可以拨打电话。接着检查foregroundCall是否处于Active状态,若是则调用switchWaitingOrHoldingAndActive将它们切换到后台,调用fakeHoldForegroundBeforeDial将前台中的连接全部清空到后台,并且状态变为HOLDING。在进行这些前期检查和准备后,创建一个GsmConnection实例即pendingMO,检查传递过来的电话号码是否有效合法,若不合法则调用pollCallsWhenSafe(),目的是将其标为dropped;若合法则设置为非静音后,调用RIL. dial进行拨号。最后,更新Phone状态并通知给注册者。

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

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

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

当上面这些功能API函数完成后,均由下面的case分支处理:

case EVENT_OPERATION_COMPLETE:
ar = (AsyncResult)msg.obj;
operationComplete();
break;

explicitCallTransfer:调用RIL.explicitCallTransfer进行交换,AT执行完成后返回的Message由handlerMessage中的case分支EVENT_ECT_RESULT进行处理。

switchWaitingOrHoldingAndActive():调用RIL.witchWaitingOrHoldingAndActive进行切换,AT执行完成后返回的Message由handlerMessage中的case分支EVENT_SWITCH_RESULT进行处理。

conference():调用RIL.conference进行电话会议,AT执行完成后返回的Message由handlerMessage中的case分支EVENT_CONFERENCE_RESULT进行处理。

separate:调用RIL.separateConnection分离出一路通话,AT执行完成后返回的Message由handlerMessage中的case分支EVENT_SEPARATE_RESULT进行处理。

对它们执行结果的处理如下:

case EVENT_SWITCH_RESULT:
case EVENT_CONFERENCE_RESULT:
case EVENT_SEPARATE_RESULT:
case EVENT_ECT_RESULT:
ar = (AsyncResult)msg.obj;
if (ar.exception != null){
phone.notifySuppServiceFailed(getFailedService(msg.what));
}
operationComplete();
break;

在未发生异常的情况下,会调用operationComplete()检查是否是最后一个操作,若是则发送EVENT_POLL_CALLS_RESULT类型消息,进而继续由handlerMessage处理:

case EVENT_POLL_CALLS_RESULT:
ar = (AsyncResult)msg.obj;//获取回送的结果
if (msg == lastRelevantPoll) {
if (DBG_POLL) log(
“handle EVENT_POLL_CALL_RESULT: set needsPoll=F”);
needsPoll = false;
lastRelevantPoll = null;
handlePollCalls((AsyncResult)msg.obj);//处理结果
}
break;

handlePollCalls处理查询结果,将回送的Modem中的通话列表逐个解析出来。

其它函数解释:

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):挂断所有电话通路

 

获取通话状态

Android提供的接口:TelephonyManager和PhoneStateListener

提供了诸如数据连接状态(连接、未连接)和传输方向(上传或下载等)、电话状态(空闲IDLE、摘机OFFHOOK、RINGING响铃)。OFFHOOK状态起始于开始拨打电话(包括还未接通)或开始接听电话到电话被挂断这个时间段。API中未区分开始拨叫到对方应答这一状态。

这样,如果只用API的话就无法获取准确的拨叫计时(从接通开始的那一刻)信息,只能得到从开始呼叫的那一刻开始的计时信息。

但在底层的库中电话状态详细区分了这些状态,见Call类。它定义了更详细的状态:

IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED;

Call类只是个抽象类,GSM和CDMA手机有其自己的实现。其中GSM的实现是GsmCall,其状态的更新依赖于DriverCall类。在DriverCall中,使用ATResponseParser解析AT命令CLCC的执行结果,即可得知通话状态:

+CLCC: 1,0,2,0,0,”+18005551212″,145 index,isMT,state,mode,isMpty(,number,TOA)

具体见DriverCall.stateFromCLCC函数:

switch(state) {

case 0: return State.ACTIVE;

case 1: return State.HOLDING;

case 2: return State.DIALING;

case 3: return State.ALERTING;

case 4: return State.INCOMING;

case 5: return State.WAITING;

default:

throw new ATParseEx(“illegal call state ” + state);

}

 

这样,如果想获知通话状态从呼叫开始到呼叫接通这一变化,只能使用底层的未开放的API:

Phone phone = PhoneFactory.getDefaultPhone();

Call.State state = phone.getForegroundCall().getState();

因此,这种需要调用底层API的程序就不能在SDK上进行开发。它必须和Android源代码一起编译。使用它的代价是很可能导致这种程序在不同Android平台上的不兼容,如运行不正确甚至根本不能运行等。

 

数据连接管理

数据连接管理是由GsmDataConnectionTracker来完成。

boolean trySetupData(String reason) 尝试建立数据连接。它只是检查当前手机状态是具备建立数据连接的条件,当符合条件时,就去建立连接。这些条件包括当前是否处在正常的非连接状态、phone是否处在空闲状态、SIM卡是否就绪、是否允许数据连接、数据连接是否受限、以及电源状态也允许的情况下。接着调用buildWaitingApns去构建一个可供使用的APN列表(用户设置的preferred APN,若没设置则APN的类型匹配亦可,详见buildWaitingApns说明),若列表不为空,若就调用setupData建立数据连接。

boolean setupData(String reason)

setupData使用PdpConnection的connect函数(最终调用到RIL的setupDataCall函数),从备选APN列表waitingApns中,选择第一个APN作为配置去建立数据连接,详见buildWaitingApns函数。

 

APN的使用

createAllApnList:用当前SIM卡对应的运营商查询系统的所有APN,并调用createApnList

创建APN列表,并找到用户设置的preferred APN

createApnList:用查询得到的数据集,创建APN列表

onApnChanged:当APN被用户更改时,将调用到此函数,重新建立数据连接

buildWaitingApns:使用用户设置的preferred APN构建一个可用于数据连接的备选APN列表,即waitingApns列表(当有preferred APN,该列表就只有一个)。若用户没有设置preferred APN,则将所有类型匹配的APN添加到waitingApns列表(如default类型)。当trySetupData函数检查有waitingApns列表中有可用的APN时,就会去尝试建立连接。

getNextApn():当PdpConnection需要用到APN建立数据连接时,将调用该函数从waitingApns备选列表中获取第一个APN,直至成功建立数据连接为止。

setPreferredApn:当用户没有设置preferred APN时,将当前数据连接成功的那个APN设置为preferred APN。详见onDataSetupComplete。

getPreferredApn:用户获取用户设置的preferred APN

网络信息

包括IP地址、网关和DNS信息,应使用GSMPhone

getIpAddress

getGateway

getDnsServers

上层应用程序应该用GSMPhone来使用这些API

posted @ 2014-07-11 15:49  xiaoaidelala  阅读(2794)  评论(0编辑  收藏  举报