frameworks/opt/telephony/src/java/com/android/internal/telephony/CallTracker.java
CallTracker同字面意思就是依据ril上报消息,维护framework层通话数据结构的类。子类有多个,例如GsmCallTracker,CdmaCallTracker
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmCallTracker.java
static final int MAX_CONNECTIONS = 7; // only 7 connections allowed in GSM static final int MAX_CONNECTIONS_PER_CALL = 5; // only 5 connections allowed per call两个常量,可以看出Gsm的通话中最多可以有7个Connection,而每一个Call最多可以有5个Connection //***** Instance Variables GsmConnection mConnections[] = new GsmConnection[MAX_CONNECTIONS];mConnections数组,保存所有的Connection GsmCall mRingingCall = new GsmCall(this); // A call that is ringing or (call) waiting GsmCall mForegroundCall = new GsmCall(this); GsmCall mBackgroundCall = new GsmCall(this);拥有三个Call的实例,分别代表了来电,前台通话和后台通话。一个Call可以有1个或者多个Connection,但是也是有限制的,RingingCall就能只有1个Connection,而且只有一个Call是会议通话即最多5个Connection,另一个Call只能有1个Connection。那么之前7个Connection的情况,就是前台和后台分别有5个和1个(1个和5个),来电一个,加起来是7个。 GsmConnection mPendingMO;成员变量mPendingMo,拨号的时候会用到。 synchronized Connection dial (String dialString, int clirMode, UUSInfo uusInfo, Bundle intentExtras) throws CallStateException { // note that this triggers call state changed notif clearDisconnected(); if (!canDial()) { //多种条件判断当前是否可以拨号,例如当前正在拨号未接通就不可以拨号 throw new CallStateException("cannot dial in current state"); } String origNumber = dialString; dialString = convertNumberIfNecessary(mPhone, dialString); // The new call must be assigned to the foreground call. // That call must be idle, so place anything that's // there on hold if (mForegroundCall.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 /// M: CC015: CRSS special handling @{ mWaitingForHoldRequest.set(); /// @} switchWaitingOrHoldingAndActive(); //先切换前台通话到后台 // This is a hack to delay DIAL so that it is sent out to RIL only after // EVENT_SWITCH_RESULT is received. We've seen failures when adding a new call to // multi-way conference calls due to DIAL being sent out before SWITCH is processed try { Thread.sleep(500); //等待modem切换,modem操作要一定时间的 } catch (InterruptedException e) { // do nothing } // 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 (mForegroundCall.getState() != GsmCall.State.IDLE) { //we should have failed in !canDial() above before we get here throw new CallStateException("cannot dial in current state"); } mPendingMO = new GsmConnection(mPhone.getContext(), checkForTestEmergencyNumber(dialString), this, mForegroundCall); //创建一个新的GsmConnection,赋值给mPendingMo mHangupPendingMO = false; if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0 || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0 ) {//拨号号码为空或者有*字符,为非法号码,不拨号 // Phone number is invalid mPendingMO.mCause = DisconnectCause.INVALID_NUMBER; /// M: CC015: CRSS special handling @{ mWaitingForHoldRequest.reset(); /// @} // handlePollCalls() will notice this call not present // and will mark it as dropped. pollCallsWhenSafe(); } else { // Always unmute when initiating a new call setMute(false);//拨号会取消静音 /// M: CC015: CRSS special handling @{ if (!mWaitingForHoldRequest.isWaiting()) { /// M: CC010: Add RIL interface @{ if (PhoneNumberUtils.isEmergencyNumber(mPhone.getSubId(), dialString) && !PhoneNumberUtils.isSpecialEmergencyNumber(dialString)) { int serviceCategory = PhoneNumberUtils.getServiceCategoryFromEcc(dialString); mCi.setEccServiceCategory(serviceCategory); mCi.emergencyDial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage(EVENT_DIAL_CALL_RESULT)); //紧急号码拨号 /// @} } else { mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage(EVENT_DIAL_CALL_RESULT)); //普通拨号 } } else { mWaitingForHoldRequest.set(mPendingMO.getAddress(), clirMode, uusInfo); } /// @} } if (mNumberConverted) { mPendingMO.setConverted(origNumber); mNumberConverted = false; } updatePhoneState(); mPhone.notifyPreciseCallStateChanged(); return mPendingMO; }流程看我写的注释,拨号时传递了EVENT_DIAL_CALL_RESULT消息。 case EVENT_DIAL_CALL_RESULT: ... operationComplete();消息处理调用operationComplete,这个也是通话操作(交换通话,主动挂断,通话中断等)完毕后收到ril响应时的标准操作 private void operationComplete() { mPendingOperations--; //操作数-1,表示又完成了一个操作,这个数量是在obtainCompleteMessage中加1的 if (DBG_POLL) log("operationComplete: pendingOperations=" + mPendingOperations + ", needsPoll=" + mNeedsPoll); if (mPendingOperations == 0 && mNeedsPoll) { mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT); mCi.getCurrentCalls(mLastRelevantPoll); //向ril发起状态查询 } else if (mPendingOperations < 0) { // this should never happen Rlog.e(LOG_TAG,"GsmCallTracker.pendingOperations < 0"); mPendingOperations = 0; } }ril的getCurrentCalls函数发起的通话状态查询,这个是维护通话相关数据结构Call、Connection的起点 case EVENT_POLL_CALLS_RESULT: ... handlePollCalls((AsyncResult)msg.obj); ... 收到回应消息后调用handlePollCalls,这个函数比较长,分为三段来看: handlePollCalls(AsyncResult ar) { List polledCalls; ... polledCalls = (List)ar.result; //ril上报的数据是个list ... 首先获取了ril上报的数据 for (int i = 0, curDC = 0, dcSize = polledCalls.size() ; i < mConnections.length; i++) { //循环次数是Connection数组的个数,对gsm来说也就是7 GsmConnection conn = mConnections[i]; //从数组中获取对应索引的每一个Connection DriverCall dc = null; // polledCall list is sparse if (curDC < dcSize) { dc = (DriverCall) polledCalls.get(curDC); //强制转换为DiriverCall,这时可以看出polledCalls列表的每一项的类型了 //DriverCall是Connection的数据来源 if (dc.index == i+1) { curDC++; } else { dc = null; } } if (DBG_POLL) log("poll: conn[i=" + i + "]=" + conn+", dc=" + dc); //这里大概也能推断出这个循环的作用:依据每个DriverCall更新对应的Connection if (conn == null && dc != null) { //conn为null,dc不为null,那么用dc填充conn /* M: CC part start */ if (DBG_POLL) log("case 1 : new Call appear"); ... // Connection appeared in CLCC response that we don't know about if (mPendingMO != null && mPendingMO.compareTo(dc)) { //拨号的情况 if (DBG_POLL) log("poll: pendingMO=" + mPendingMO); ... // It's our pending mobile originating call mConnections[i] = mPendingMO; mPendingMO.mIndex = i; mPendingMO.update(dc); //依据dc更新mPendingMO状态 mPendingMO = null; //前台通话已经有mPendingMO的引用,所以这里mPendingMO可以置空了 // Someone has already asked to hangup this call if (mHangupPendingMO) { //拨号后还没成功,就马上点击挂断,会走到这里 mHangupPendingMO = false; try { if (Phone.DEBUG_PHONE) log( "poll: hangupPendingMO, hangup conn " + i); hangup(mConnections[i]); } catch (CallStateException ex) { Rlog.e(LOG_TAG, "unexpected error on hangup"); } // Do not continue processing this poll // Wait for hangup and repoll return; } } else { //其它情况,一般是来电 ... mConnections[i] = new GsmConnection(mPhone.getContext(), dc, this, i); ... } else if (checkFlag && (mConnections[i].getCall() == mRingingCall)) { // it's a ringing call newRinging = mConnections[i]; //来电 } else if (checkFlag) { ... newUnknown = mConnections[i]; unknownConnectionAppeared = true; //这种情况很罕见,但是我还是见过的,是stk程序中的拨号或者phone进程crash后会走到这里 //stk拨号是直接发送At命令,不通过telephony framework的代码;phone crash后会马上重启,这个时候相关数据结构当然是空的 } /* M: CC part end */ } hasNonHangupStateChanged = true; } else if (conn != null && dc == null) { //conn不为null,而dc为null,对应通话挂断 /* M: CC part start */ if (DBG_POLL) log("case 2 : old Call disappear"); /// M: CC019: Convert state from WAITING to INCOMING @{ if (((conn.getCall() == mForegroundCall && mForegroundCall.mConnections.size() == 1 && mBackgroundCall.isIdle()) || (conn.getCall() == mBackgroundCall && mBackgroundCall.mConnections.size() == 1 && mForegroundCall.isIdle())) && mRingingCall.getState() == GsmCall.State.WAITING) { mRingingCall.mState = GsmCall.State.INCOMING; //本来是通话中来电,然后前台或者后台通话挂断了,就变成了第一路来电了 } /// @} // Connection missing in CLCC response that we were tracking. mDroppedDuringPoll.add(conn); //加入conn到mDroppedDuringPoll中 // Dropped connections are removed from the CallTracker // list but kept in the GsmCall list mConnections[i] = null; //置空 ... } else if (conn != null && dc != null && !conn.compareTo(dc)) { //conn和dc都不为null,但是conn和dc不匹配 /* M: CC part start */ if (DBG_POLL) log("case 3 : old Call replaced"); // Connection in CLCC response does not match what // we were tracking. Assume dropped call and new call mDroppedDuringPoll.add(conn); //加入conn到mDroppedDuringPoll中 /// M: CC010: Add RIL interface @{ // give CLIP ALLOW default value, it will be changed on CLIP URC dc.numberPresentation = PhoneConstants.PRESENTATION_ALLOWED; /// @} /* M: CC part end */ mConnections[i] = new GsmConnection (mPhone.getContext(), dc, this, i); //创建新的Connection代替 if (mConnections[i].getCall() == mRingingCall) { newRinging = mConnections[i]; //有可能是新的来电 } // else something strange happened hasNonHangupStateChanged = true; } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */ //conn和dc都不为null,且conn和dc匹配,此时更新conn即可 /* M: CC part start */ if (DBG_POLL) log("case 4 : old Call update"); ... boolean changed; changed = conn.update(dc); hasNonHangupStateChanged = hasNonHangupStateChanged || changed; } ... } //循环完毕再次更新GsmPhone中mConnections数组和GsmCallTracker的一些变量值 // This is the first poll after an ATD. // We expect the pending call to appear in the list // If it does not, we land here //正常情况下mPendingMO所指向的Connection已经由前台通话维护了,这里应该为null。如果不为null,这里特殊处理 if (mPendingMO != null) { Rlog.d(LOG_TAG,"Pending MO dropped before poll fg state:" + mForegroundCall.getState()); mDroppedDuringPoll.add(mPendingMO); //丢弃mPendingMO,其实号码已经实际拨出,后续拨号成功后会作为unknown connection出现的 mPendingMO = null; mHangupPendingMO = false; } if (newRinging != null) { if (DBG_POLL) log("notifyNewRingingConnection"); mPhone.notifyNewRingingConnection(newRinging); //向上层app通知来电 ... } // clear the "local hangup" and "missed/rejected call" // cases from the "dropped during poll" list // These cases need no "last call fail" reason log("dropped during poll size = " + mDroppedDuringPoll.size()); for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) { //处理之前要丢弃的Connection GsmConnection conn = mDroppedDuringPoll.get(i); /// M: CC012: Set as DisconnectCause.LOCAL if conn is disconnected due to Radio Off @{ if (isCommandExceptionRadioNotAvailable(ar.exception)) { conn.onHangupLocal(); } /// @} if (conn.isIncoming() && conn.getConnectTime() == 0) { // Missed or rejected call int cause; if (conn.mCause == DisconnectCause.LOCAL) { //这里可以看出来电未接的两种挂断类型其实是代码设置的,不属于网络上报挂断类型 cause = DisconnectCause.INCOMING_REJECTED; //主动拒接来电 } else { cause = DisconnectCause.INCOMING_MISSED; //未接来电 } if (Phone.DEBUG_PHONE) { log("missed/rejected call, conn.cause=" + conn.mCause); log("setting cause to " + cause); } mDroppedDuringPoll.remove(i); hasAnyCallDisconnected |= conn.onDisconnect(cause); } else if (conn.mCause == DisconnectCause.LOCAL || conn.mCause == DisconnectCause.INVALID_NUMBER) { log("local hangup or invalid number"); mDroppedDuringPoll.remove(i); hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause); } } ... // Any non-local disconnects: determine cause if (mDroppedDuringPoll.size() > 0 && /// M: For 3G VT only @{ !hasPendingReplaceRequest) { //不是local类型的挂断,向ril请求获取挂断类型 /// @} mCi.getLastCallFailCause( obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE)); } if (needsPollDelay) { pollCallsAfterDelay(); //如有需要就再次调用ril的getCurrentCalls } // Cases when we can no longer keep disconnected Connection's // with their previous calls // 1) the phone has started to ring // 2) A Call/Connection object has changed state... // we may have switched or held or answered (but not hung up) if (newRinging != null || hasNonHangupStateChanged || hasAnyCallDisconnected) { internalClearDisconnected(); //清理所有Call中已经挂断的Connection } updatePhoneState(); //更新并发送phone state if (unknownConnectionAppeared) { if (DBG_POLL) log("notifyUnknownConnection"); mPhone.notifyUnknownConnection(newUnknown); //通知上层有unknown connection出现 } if ((hasNonHangupStateChanged || newRinging != null || hasAnyCallDisconnected) /// M: CC015: CRSS special handling @{ && !mHasPendingSwapRequest) { /// @} if (DBG_POLL) log("notifyPreciseCallStateChanged"); mPhone.notifyPreciseCallStateChanged(); //通知上层有Call状态的变化。 } ...更新后的处理。具体分析见代码中加的注释。 拨号的obtainCompleteMessage(EVENT_DIAL_CALL_RESULT)发起了获取通话状态请求,代码中另一处发起该请求的是pollCallsWhenSafe,该方法定义在基类CallTracker中 protected void pollCallsWhenSafe() { mNeedsPoll = true; if (checkNoOperationsPending()) { mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT); mCi.getCurrentCalls(mLastRelevantPoll); } }该方法在EVENT_CALL_STATE_CHANGE消息处理中被调用 case EVENT_REPOLL_AFTER_DELAY: case EVENT_CALL_STATE_CHANGE: pollCallsWhenSafe();该消息是在GsmCallTracker构造方法中注册到ril中的 mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null); 那么对应于obtainCompleteMessage是主动请求,EVENT_CALL_STATE_CHANGE消息就是被动的,在通话状态有变化的时候上报,然后修正当前通话状态相关数据结构。cdma的大体流程和gsm的类似,下面讲讲区别的地方
static final int MAX_CONNECTIONS = 8; static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per callcdma的有变化,每个Call的最大数量是1,connection数量最大是8。这个是我手头mtk6.0的代码,不过我印象中我n久之前看过的源码中MAX_CONNECTIONS=2,8肯定是不对的。因为只有三个Call,一个Call一个Connection也算不出是8呀,顶多是3。其实3也不对,cdma最多是2,不信的话可以可以拿起电信手机试试,已有两路通话下是打不进去电话的。 mCi.registerForCallWaitingInfo(this, EVENT_CALL_WAITING_INFO_CDMA, null); /// M: For CDMA call accepted @{ mCi.registerForCallAccepted(this, EVENT_CDMA_CALL_ACCEPTED, null);构造函数中的这两个是特有的,一个是监听通话中来电,gsm的来电就是一个事件,而cdma的是两个事件。
另一个是监听cdma拨号后接通。CdmaCall是没有DIALING和ALERTING这两个状态的,Call的State定义见
frameworks/opt/telephony/src/java/com/android/internal/telephony/Call.java
public enum State { IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED, DISCONNECTING; ... public boolean isDialing() { return this == DIALING || this == ALERTING; } ... }电信手机一拨出就是ACTIVE的状态,然后计时也开始走,接通后会重置计时,这个重置事件就是EVENT_CDMA_CALL_ACCEPTED。 Connection dial (String dialString, int clirMode) throws CallStateException { ... if (mForegroundCall.getState() == CdmaCall.State.ACTIVE) { return dialThreeWay(dialString); } ... }拨号大体类似,但是第二路拨号就不同了,会走dialThreeWay,这里和监听通话中来电是单独事件类似,是cdma的特殊性。 private Connection dialThreeWay (String dialString) { ... mCi.sendCDMAFeatureCode(mPendingMO.getAddress(), obtainMessage(EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA)); ... } 调用ril的sendCDMAFeatureCode拨号,不是ril的dial,这个比较奇怪。继续看怎么接听电话: void acceptCall() throws CallStateException { if (mRingingCall.getState() == CdmaCall.State.INCOMING) { Rlog.i("phone", "acceptCall: incoming..."); // Always unmute when answering a new call setMute(false); mCi.acceptCall(obtainCompleteMessage()); } else if (mRingingCall.getState() == CdmaCall.State.WAITING) { CdmaConnection cwConn = (CdmaConnection)(mRingingCall.getLatestConnection()); // Since there is no network response for supplimentary // service for CDMA, we assume call waiting is answered. // ringing Call state change to idle is in CdmaCall.detach // triggered by updateParent. cwConn.updateParent(mRingingCall, mForegroundCall); cwConn.onConnectedInOrOut(); updatePhoneState(); switchWaitingOrHoldingAndActive(); } else { throw new CallStateException("phone not ringing"); } } void switchWaitingOrHoldingAndActive() throws CallStateException { // Should we bother with this check? if (mRingingCall.getState() == CdmaCall.State.INCOMING) { throw new CallStateException("cannot be in the incoming state"); } else if (mForegroundCall.getConnections().size() > 1) { flashAndSetGenericTrue(); } else { // Send a flash command to CDMA network for putting the other party on hold. // For CDMA networks which do not support this the user would just hear a beep // from the network. For CDMA networks which do support it will put the other // party on hold. mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT)); } } private void flashAndSetGenericTrue() { mCi.sendCDMAFeatureCode("", obtainMessage(EVENT_SWITCH_RESULT)); // Set generic to true because in CDMA it is not known what // the status of the call is after a call waiting is answered, // 3 way call merged or a switch between calls. mForegroundCall.setGeneric(true); mPhone.notifyPreciseCallStateChanged(); }接听第一路和第二路又是不一样的分支,而且接听第二路居然使用的是switchWaitingOrHoldingAndActive,这个方法不是交换通话时用的吗?而switchWaitingOrHoldingAndActive最后用的还是sendCDMAFeatureCode。还可以看下合并通话的的方法:
void conference() { // Should we be checking state? flashAndSetGenericTrue(); }一样是最终使用sendCDMAFeatureCode。
这个其实就是cdma的特殊之处,和gsm差异很大的。还记得小时候央视有个联通的广告是这样的:一个小年轻说自己头疼,这时老妈跳出来说你这是天天用手机通话时间长的原因(明着说移动),然后掏出一个联通手机说用cdma好啊,信令少,打多久都不头疼。这个信令少确实说的对,从代码中可见cdma手机进入多方通话状态的时候,所有的命令其实都是一个命令,无论是接听来电、拨号、交换通话还是合并通话。交换通话和合并通话是一个命令是这样的:当第二路通话是拨号的时候,只有合并通话的功能;第二路通话是来电的时候,只有交换通话的功能。那么挂断第二路通话的时候怎么办?和接通电话不冲突吗?见代码
挂断通话中来电: /*package*/ void hangup (CdmaConnection conn) throws CallStateException { ... } else if ((conn.getCall() == mRingingCall) && (mRingingCall.getState() == CdmaCall.State.WAITING)) { /// M: @{ log("hangup waiting call"); /// @} // Handle call waiting hang up case. // // The ringingCall state will change to IDLE in CdmaCall.detach // if the ringing call connection size is 0. We don't specifically // set the ringing call state to IDLE here to avoid a race condition // where a new call waiting could get a hang up from an old call // waiting ringingCall. // // PhoneApp does the call log itself since only PhoneApp knows // the hangup reason is user ignoring or timing out. So conn.onDisconnect() // is not called here. Instead, conn.onLocalDisconnect() is called. conn.onLocalDisconnect(); updatePhoneState(); mPhone.notifyPreciseCallStateChanged(); return; ... }并没有向ril发送什么命令,只是把framework层的来电结构release了,这样app就以为电话是挂断的。针对cdma的这种特点,在进入多方通话的状态后会运行下面这句代码:
mForegroundCall.setGeneric(true);这句代码就在上面的flashAndSetGenericTrue方法里,这个定义在Call.java中 /** * To indicate if the connection information is accurate * or not. false means accurate. Only used for CDMA. */ public boolean isGeneric() { return mIsGeneric; }可见这个是专门针对cdma而设置的,标记当前是否已经进入多方通话。当mIsGeneric为true的时候,app UI不会像gsm一样精确的显示每个Connection信息。cdma也没有gsm的会议管理功能,所以也就没有会议管理中分离Connection的功能和挂断Connection的功能。mIsGeneric为true的时候也不会接收到对方传来的挂断消息,只能自己主动挂断,同时主动挂断会挂断所有的通话。
