Communication - 02.Call U

App层


  从大拇哥Click CallButton开始手机便已明白,主人这是要打电话。当然,你可以选择直接拨号,也可以通过ContactList,或者从通话记录着手。这些都只是UI的设计不同而已,终归都会有一个统一的入口开始Calling。这个汇合点就是:

android:targetActivity="OutgoingCallBroadcaster"

  这是一个独立的Activity,你可以设计各种花里胡哨的拨号方式、Activities,而后通过startActivity跨应用访问来开始 OutgoingCallBroadcaster的生命周期。

  CallController.placeCall调用PhoneUtils.placeCall之后,便由app层进入到了framework层。

  可以看出app层没有什么太复杂的逻辑,重点还是OutgoingCallBroadcaster之前的UI发挥。

Framework层


  作为办实事的framework层,CallManager类 需要知道手机使用的是何种制式的网络,从而调用该制式的类的方法。显然,这里有“工厂方法”的影子。而GsmCallTracker类 调用RIL去发送AT。同时也需要监控Calling的各种状态返回给上层。

public interface Phone
public class PhoneProxy extends Handler implements Phone {};
public abstract class PhoneBase extends Handler implements Phone {}
public class GSMPhone extends PhoneBase {}

  从上图可以看出,我们的首要目的是让上层用户获得一个XXXPhone。

  PhoneApp类 中 onCreate方法 调用PhoneFactory类的静态方法 makeDefaultPhones():

  • PhoneFactory
sCommandsInterface = new RIL(context, networkMode, cdmaSubscription);
int phoneType = getPhoneType(networkMode);

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”);
}

  Android的应用程序可以使用 PhoneFactory.getDefaultPhone 来获得Phone对象,从而进行一些调用操作。

phone = PhoneFactory.getDefaultPhone();    //返回的是代理,因为面向对象中多态特性适用于多种制式的phone

  这样,上层用户便获得了XXXPhone,但却是个代理。

  到此为止,app.mCM.dial()中,直接调用GSMPhone.dial(),而GSMPhone对象将通话能力交给GsmCallTracker类 管理和维护。

  • GsmCallTracker类

  GsmCallTracker类在 GSMPhone的构造函数 中创建。

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);

  从Tracker可见其与 Handler消息机制 的相关性,而GSMCallTracker在本质上就是一个Handler,那么Tracker与底层的通信机制,也就是与RIL的前端RILJ(ril java)的communication是关键。GsmCallTracker端注册三个EVENT,从而接收并响应RIL对象发出的三种类型的Handler消息。

    //***** Constructors

    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);
}

  handleMessage方法中接收并响应RIL对象发出的 Handler回调消息类型,其中对应以上的三个EVENT:

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

  

  发现状态变化,最终都会调用cm.getCurrentCalls方法,向RIL对象查询当前Call List。RIL处理完毕,再次向上层给Tracker发送消息。

  handleMessage方法中接收,并调用handlePollCalls,根据Call List当前所有的通话连接完成通话状态的更新。

    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;

  

  •   通话管理模型

  Modem收到AT指令返回字符串返回值,解析创建 DriverCall对象列表,该列表能够真实反映出Modem无线通信模块中所有通话连接的真实信息!

  GsmConnection对象表示一个通话连接,根据DriverCall的一些基本信息创建,并更新。

  GsmConnection更新的同时同步调用所属的GsmCall对象,并更新相关信息。

  在 GSMCallTracker 中维护着通话列表,顺序记录了正连接上的通话状态。

三路电话,每一路默认最大7个连接:
GsmCall ringingCall = new GsmCall(this);
GsmCall foregroundCall = new GsmCall(this);
GsmCall backgroundCall = new GsmCall(this);

  分为了三个类别进行管理:

RingingCall:      INCOMING ,WAITING

ForegourndCall:   ACTIVE, DIALING ,ALERTING

BackgroundCall:   HOLDING   

  GSMCallTracker通过GsmConnection与DriverCall之间的比较 从而判断通话连接状况的前后变化,改变通话状态相关信息做出相应调整。

  handlePollCalls((AsyncResult)msg.obj) 解析出最新通话连接状况,与上一次connection状态比较。而后更新通话相关信息,主要是CallTracker对象中的state,  connections,  foregroundCall,  backgroundCall,  ringingCall对象的更新。

  OK, 让我们来继续拨号:

    Connection
dial (String dialString, int clirMode, UUSInfo uusInfo) throws CallStateException {
// note that this triggers call state changed notif
clearDisconnected(); if (!canDial()) {
throw new CallStateException("cannot dial in current state");
} // The new call must be assigned to the foreground call.
// That call must be idle, so place anything that's
// there on hold
if (foregroundCall.getState() == GsmCall.State.ACTIVE) {
// this will probably be done by the radio anyway
// but the dial might fail before this happens
// and we need to make sure the foreground call is clear
// for the newly dialed connection
switchWaitingOrHoldingAndActive(); // Fake local state so that
// a) foregroundCall is empty for the newly dialed connection
// b) hasNonHangupStateChanged remains false in the
// next poll, so that we don't clear a failed dialing call
fakeHoldForegroundBeforeDial();
} if (foregroundCall.getState() != GsmCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
} // Dialing, prepare to create a connection
pendingMO = new GsmConnection(phone.getContext(), dialString, this, foregroundCall);
hangupPendingMO = false; if (pendingMO.address == null || pendingMO.address.length() == 0
|| pendingMO.address.indexOf(PhoneNumberUtils.WILD) >= 0
) {
// Phone number is invalid
pendingMO.cause = Connection.DisconnectCause.INVALID_NUMBER; // handlePollCalls() will notice this call not present
// and will mark it as dropped.
pollCallsWhenSafe();
} else {
// Always unmute when initiating a new call
setMute(false); // Dialing... goto RIL JAVA
cm.dial(pendingMO.address, clirMode, uusInfo, obtainCompleteMessage());
} updatePhoneState();
phone.notifyPreciseCallStateChanged(); return pendingMO;
}

RILJ层


  RILJ层,即为RIL的最上层,也就是JAVA编写的部分,主要用于Handler交互。

    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());
} if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); send(rr);
}

  主要分为两部分:1.构建RILRequest;2. 把它发出去。

    private void
send(RILRequest rr) {
Message msg; if (mSocket == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
return;
} msg = mSender.obtainMessage(EVENT_SEND, rr); acquireWakeLock(); msg.sendToTarget();
}

  

  Target就是 class RILSender。

  RILJ作为RIL的最前端与framework通过handler交流,与下层则通过socket通信。有点“承前启后”的意思。

        @Override public void
handleMessage
(Message msg) {
RILRequest rr = (RILRequest)(msg.obj);
RILRequest req = null; switch (msg.what) {
case EVENT_SEND:
/**
* mRequestMessagePending++ already happened for every
* EVENT_SEND, thus we must make sure
* mRequestMessagePending-- happens once and only once
*/
boolean alreadySubtracted = false;
try {
LocalSocket s; s = mSocket; if (s == null) {
rr.onError(RADIO_NOT_AVAILABLE, null);
rr.release();
if (mRequestMessagesPending > 0)
mRequestMessagesPending--;
alreadySubtracted = true;
return;
} synchronized (mRequestsList) {
mRequestsList.add(rr);
mRequestMessagesWaiting++;
} if (mRequestMessagesPending > 0)
mRequestMessagesPending--;
alreadySubtracted = true; byte[] data; data = rr.mp.marshall();
rr.mp.recycle();
rr.mp = null; if (data.length > RIL_MAX_COMMAND_BYTES) {
throw new RuntimeException(
"Parcel larger than max bytes allowed! "
+ data.length);
} // parcel length in big endian
dataLength[0] = dataLength[1] = 0;
dataLength[2] = (byte)((data.length >> 8) & 0xff);
dataLength[3] = (byte)((data.length) & 0xff); //Log.v(LOG_TAG, "writing packet: " + data.length + " bytes"); s.getOutputStream().write(dataLength);
s.getOutputStream().write(data);
}
            ... ...
case EVENT_WAKE_LOCK_TIMEOUT:

  最后将数据写入通过LocalSocket连接对象获取输出流。

  以上便是Java部分,RIL的重难点在于下面的C部分。简单的阐述,只是针对dial,但对流程的理解多少有点帮助。

  Telephony的架构有太多细节可以探究,有志者可以摸索,“自己动脑丰衣足食”,本人摸索完毕后就不再此赘述了。

补充:看到园内一篇介绍开会注意事项的随笔,其中有云:戒条八不要忘记把会议总结发送给与会人

我这也算是个人的学习总结性随笔,好记性不如烂笔头。

  

HAPPY WEEKEND:-)

 
 
分类: Communication

摘要: App层 从大拇哥Click CallButton开始手机便已明白,主人这是要打电话。当然,你可以选择直接拨号,也可以通过ContactList,或者从通话记录着手。这些都只是UI的设计不同而已,终归都会有一个统一的入口开始Calling。这个汇合点就是:android:targetActivity="OutgoingCallBroadcaster" 这是一个独立的Activity,你可以设计各种花里胡哨的拨号方式、Activities,而后通过startActivity跨应用访问来开始 OutgoingCallBroadcaster的生命周期。 CallControlle阅读全文
posted @ 2013-06-28 18:07 郝一二三 阅读(364) | 评论 (0) 编辑
 
摘要: 冷落博客已有一年,理由种种,想来是腾出了些时间,但未见得其他方面有了什么可观的进步。打理博客犹如健身,每天不抬几次杠铃活动活动筋骨则憋的荒。消耗了大量的体力,一天下来却倍感清爽,人清爽了做什么都很来劲儿,久而久之反而能积累不少成果。写博客,百利而无一害也。 最近一年与往年相比相差甚远,不管是Positivity、Endurance还是各种Ablilities,好比黄金圣斗士堕落到了青铜级别,更可怕的是对技术越感麻木,难道这就是所谓的技术阶段性更年期?思前想后确实有一点变了:”过去把技术视作目标,现在更愿意把技术作为达成其他目标的手段。“这种思想观念的变化有利有弊,也可能预示着什么。写点儿...阅读全文
posted @ 2013-06-19 13:23 郝一二三 阅读(544) | 评论 (1) 编辑

随机推荐

  1. Xutils呼叫流源代码文件下载方法

    //我主要是好奇Xutils哪里回调onLoading(),查找等了很久也没找到,果然easy查找只是把它写下来 前言: 1.代码摘要只有主线,提供一般流程 2.为了易于理解,码变量名,而是类名的驼峰 ...

  2. TRS_WCM(拓尔思信息技术有限公司)内容协作平台平台置标经验攻略

    TRS_WCM置标过程中经验积累 版本V4.0-2014.6.24-穿越者7号 目录 1.嵌套模板置标 1 2.栏目名称超链接置标 1 3.列表循环输出文档标题包含超链接 1 4.取既定栏目下第一篇文 ...

  3. [Unity-7] Update和FixedUpdate

    1.Update和FixedUpdate这是Unity既用内提供的帧功能接口相关联. Update():这个函数里面的内容每一帧都会被运行一次.这个函数有一个特点,那就是运行的频率等于帧率.而这个帧率 ...

  4. 为JS字符类型添加trim方法

    JS字符串本身并不没有提供常用的trim方法,我们可以通过修改String原形来添加此方法,我们就可以直接调用此方法了: String.prototype.trim = function(){retu ...

  5. OpenSUSE13.2安装MongoDB

    真是一个悲伤的故事,就是你解决过得问题没有记住,却需要再通过搜索引擎来找一遍,幸运的是曾经你做过记录,搜索帮你找到了. 这是我一个Wordpress博客整理记录的,好久没在那里更新了,两个月的时间,我 ...

  6. VBOX安装Centos设置分辨率为1366x768[已解决]

    最近想了解下GTK+,但是对于直接在系统上搭建环境有点心里阴影,怕又把桌面玩挂,所以打算在虚拟机中先试试 本来打算使用Fedora的,怕gnome太吃资源所以下了个xfce的,不过貌似有BUG,无法安 ...

  7. [译]JDK 6 and JDK 7中的subString()方法

    (说明,该文章翻译自The substring() Method in JDK 6 and JDK 7) 在JDK 6 and JDK 7中的substring(int beginIndex, int ...

  8. solr的配置文件及其含义

    solr与.net系列课程(二)solr的配置文件及其含义    solr与.net系列课程(二)solr的配置文件及其含义  本节内容还是不会涉及到.net与数据库的内容,但是不要着急,这都是学时s ...

  9. ASP.NET MVC项目里创建一个aspx视图

    先从控制器里添加视图 视图引擎选"ASPX(C#)",使用布局或模板页不要选. 在Views\EAV目录里,生成的aspx是个单独的页面,没有代码文件,所以代码也要写在这个文件里. ...

  10. WCF、Web API、WCF REST、Web Service

    WCF.Web API.WCF REST.Web Service 区别 Web Service It is based on SOAP and return data in XML form. It ...