frameworks/opt/telephony/src/java/com/android/internal/telephony/CallTracker.java
CallTracker同字面意思就是依据ril上报消息,维护framework层通话数据结构的类。子类有多个,例如GsmCallTracker,CdmaCallTracker
GsmCallTracker
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消息就是被动的,在通话状态有变化的时候上报,然后修正当前通话状态相关数据结构。
CdmaCallTracker
frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaCallTracker.javacdma的大体流程和gsm的类似,下面讲讲区别的地方
static final int MAX_CONNECTIONS = 8;
static final int MAX_CONNECTIONS_PER_CALL = 1; // only 1 connection allowed per call
cdma的有变化,每个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的时候也不会接收到对方传来的挂断消息,只能自己主动挂断,同时主动挂断会挂断所有的通话。