Android Telephony 9.0通话挂断连接处理机制(opt/Telephony)_sed

 前言:今天看了一下通话断开处理流程,所以做一个笔记来记录一下今天的学习成果。

通话断开连接一般有两种应用场景

  • 本地主动挂通话
  • 远端断开通话连接 (这里还包括网络挂断和对方挂断)

 

先处理本地挂断

本地主动挂断通话

    我们不讲从Dialer点击时流程,直接进入ConnectionService类,ConnectionService类接收到Telecom系统应用发起的hanup()“挂断”,通过Telecom callId匹配TelephonyConnection对象并调用其onDisconnect方法进行挂断请求,以我写博客的 路子 ,一般先画图进行讲解  ^_^。

本地主动挂断通话流程图:

Android Telephony 9.0通话挂断连接处理机制(opt/Telephony)_sed_02

接下来开始luo代码

ConnectionService.java

private void disconnect(String callId) {
Log.d(this, "disconnect %s", callId);
if (mConnectionById.containsKey(callId)) {
findConnectionForAction(callId, "disconnect").onDisconnect();
} else {
findConferenceForAction(callId, "disconnect").onDisconnect();
}
}

     这里的findConnectionForAction是mConnectionById获取TeleService中的Connection(他的实现对象就是TelephonyConnection)对象

TelephonyConnection.java
 

@Override
public void onDisconnect() {
Log.v(this, "onDisconnect");
mHandler.obtainMessage(MSG_HANGUP,
android.telephony.DisconnectCause.LOCAL).sendToTarget();
PhoneNumberUtils.resetCountryDetectorInfo();
}

private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
.......
case MSG_HANGUP://挂断流程
int cause = (int) msg.obj;
hangup(cause);
break;
.......
}
}

protected void hangup(int telephonyDisconnectCode) {
......
mOriginalConnection.hangup();
......
}

我们直接看hangup()方法  这里的mOriginalConnection就是对于opt/Telephony里面的Connection对象他只有一个实现类就是GsmCdmaConnection类,mOriginalConnection后面还会做讲解这一块需要分开讲解,对应着Connection的区分关系。

GsmCdmaConnection.java

@Override
public void hangup() throws CallStateException {
if (!mDisconnected) {
mOwner.hangup(this);
} else {
throw new CallStateException ("disconnected");
}
}

这里的mOwner所属GsmCdmaCallTracker对象,这里会提供分开一个模块做讲解这里涉及到  通话模型管理 还没写 o(* ̄︶ ̄*)o

GsmCdmaCallTracker.java

//***** Called from GsmCdmaConnection

public void hangup(GsmCdmaConnection conn) throws CallStateException {
if (conn.mOwner != this) {
throw new CallStateException ("GsmCdmaConnection " + conn
+ "does not belong to GsmCdmaCallTracker " + this);
}

if (conn == mPendingMO) {
// Re-start Ecm timer when an uncompleted emergency call ends
if (mIsEcmTimerCanceled) {
handleEcmTimer(EcbmHandler.RESTART_ECM_TIMER);
}

log("hangup conn with callId '-1' as there is no DIAL response yet ");
mCi.hangupConnection(-1, obtainCompleteMessage());
} else if (!isPhoneTypeGsm()
&& conn.getCall() == mRingingCall
&& mRingingCall.getState() == GsmCdmaCall.State.WAITING) { //挂断不执行这里

conn.onLocalDisconnect();

updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return;
} else {//挂断流程入口
try {
mMetrics.writeRilHangup(mPhone.getPhoneId(), conn, conn.getGsmCdmaIndex());
mCi.hangupConnection (conn.getGsmCdmaIndex(), obtainCompleteMessage());
} catch (CallStateException ex) {
// Ignore "connection not found"
// Call may have hung up already
Rlog.w(LOG_TAG,"GsmCdmaCallTracker WARN: hangup() on absent connection "
+ conn);
}
}

conn.onHangupLocal();
}

设置挂断原因

这里会调用RIL中的mCi.hangupXXX()方法进行挂断,他会携带一个消息那就是EVENT_OPERATION_COMPLETE他会回调进入Handle,这里其实前面还有一个步骤这里做一下讲解
前面挂断过程中会携带一个参数进入RIL
EVENT_OPERATION_COMPLETE 这里也会回调Handler
通过他在去调用operationComplete(); 
这个方法中向RIL发起了一个EVENT_POLL_CALLS_RESULT
这个参数就不多讲解了 向Modem获取最新的CallList,进入 handlePollCalls

我们查看handlePollCalls()这个方法,这里属于  响应通话连接断开的处理逻辑    这里的响应二字注意

protected synchronized void handlePollCalls(AsyncResult ar) {//handlePollCalls 处理4种状态后,通知 phone 更新状态。

//===============================================
//==================对电话断开做处理=============
//===============================================
ArrayList<GsmCdmaConnection> locallyDisconnectedConnections = new ArrayList<>();
for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {
GsmCdmaConnection conn = mDroppedDuringPoll.get(i);//取出通话断开连接
//CDMA
boolean wasDisconnected = false;
//来电处理,本地挂断或者未接,本地挂断的话直接设置挂断的原因为LOCAL或INVALID_NUMBER
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); //从mDroppedDuringPoll列表移除
hasAnyCallDisconnected |= conn.onDisconnect(cause);//在次更新Connection对象
wasDisconnected = true;
locallyDisconnectedConnections.add(conn);//记录拒接来电或漏接来电
} else if (conn.mCause == DisconnectCause.LOCAL
|| conn.mCause == DisconnectCause.INVALID_NUMBER) {//电话关断入口 挂断通话
mDroppedDuringPoll.remove(i);
hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);//挂断入口
wasDisconnected = true;
locallyDisconnectedConnections.add(conn);
}

if (!isPhoneTypeGsm() && wasDisconnected && unknownConnectionAppeared
&& conn == newUnknownConnectionCdma) {
unknownConnectionAppeared = false;
newUnknownConnectionCdma = null;
}
}
if (locallyDisconnectedConnections.size() > 0) {
mMetrics.writeRilCallList(mPhone.getPhoneId(), locallyDisconnectedConnections);
}

/* Disconnect any pending Handover connections */
for (Iterator<Connection> it = mHandoverConnections.iterator();
it.hasNext();) {
Connection hoConnection = it.next();
log("handlePollCalls - disconnect hoCnotallow= " + hoConnection +
" hoConn.State= " + hoConnection.getState());
if (hoConnection.getState().isRinging()) {
hoConnection.onDisconnect(DisconnectCause.INCOMING_MISSED);
} else {
hoConnection.onDisconnect(DisconnectCause.NOT_VALID);
}
// TODO: Do we need to update these hoConnections in Metrics ?
it.remove();
}

// Any non-local disconnects: determine cause 任何非本地断开:确定原因 ,确定非本地挂断开通话的原因,需要查询Modem
if (mDroppedDuringPoll.size() > 0) {
//GsmCdmaCallTracker对象会向RILJ对象查询最后一路通话连接断开的原因,RIL处理完成后,
//回调Handler消息类型为,EVENT_GET_LAST_CALL_FAIL_CAUSE
mCi.getLastCallFailCause(
obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
}
}

这里我们重点 看一下 onDisconnect(cause);和locallyDisconnectedConnections.add(conn)
分别是  onDisconnect(cause);在此更新Connection对象
locallyDisconnectedConnections.add(conn)记录拒接来电或漏接来电

/**
* Called when the radio indicates the connection has been disconnected.
* @param cause call disconnect cause; values are defined in {@link DisconnectCause}
*
* 本地挂断处理
*/
@Override
public boolean onDisconnect(int cause) {
boolean changed = false;
mCause = cause;//断开原因
if (!mDisconnected) {//是否断开连接
doDisconnect();
if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
mOwner.getPhone().notifyDisconnect(this);
notifyDisconnect(cause);
if (mParent != null) {
//GsmCdmaCall对象的更新,关键是设置其状态为DISCONNECTED
changed = mParent.connectionDisconnected(this);
}
mOrigConnection = null;
}
clearPostDialListeners();
releaseWakeLock();
return changed;
}


private void doDisconnect() {
mIndex = -1;//重新设置索引
mDisconnectTime = System.currentTimeMillis();//记录通话断开时间
mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;//计算通话时长 通过连接时间减去当前时间等于通话时间
mDisconnected = true;//设置通话连接已断开
clearPostDialListeners();
}

本地挂断讲解完毕,下面来讲解远端管段连接

远端断开通话连接

         本地主动挂断通话连接时,会将对应的conn对象的mCause设置为Local,从而远端挂断开通话连接时,GsmCdmaCallTracker对象向RILJ对象查询通话连接断开的原因。这里就不画图了有点难 o(* ̄︶ ̄*)o

我们这里直接分析handlePollCalls方法后,处理完本地挂断通话连接的请求之后,接着会处理是否从远端挂断电话的逻辑

if (mDroppedDuringPoll.size() > 0) {
//GsmCdmaCallTracker对象会向RILJ对象查询最后一路通话连接断开的原因,RIL处理完成后,
//回调Handler消息类型为,EVENT_GET_LAST_CALL_FAIL_CAUSE
mCi.getLastCallFailCause(
obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
}

GsmCdmaCallTracker对象会向RILJ对象查询最后通话连接断开的原因,RIL处理完后,回调的Handle消息类型为EVENT_GET_LAST_CALL_FAIL_CAUSE。

接下来我们查看 EVENT_GET_LAST_CALL_FAIL_CAUSE消息响应

case EVENT_GET_LAST_CALL_FAIL_CAUSE://通话挂断查询完毕的回调,入口在handlerPollCalls()
int causeCode;//声明通话连接断开原因编号
String vendorCause = null;
ar = (AsyncResult)msg.obj;

operationComplete();//完成查询操作

if (ar.exception != null) {//异常处理 CallFailCause.NORMAL_CLEARING;
// An exception occurred...just treat the disconnect
// cause as "normal"
causeCode = CallFailCause.NORMAL_CLEARING;
Rlog.i(LOG_TAG,
"Exception during getLastCallFailCause, assuming normal disconnect");
} else {
LastCallFailCause failCause = (LastCallFailCause)ar.result;
causeCode = failCause.causeCode;//通话断开查结果
vendorCause = failCause.vendorCause;
}
// Log the causeCode if its not normal 异常原因日志记录
if (causeCode == CallFailCause.NO_CIRCUIT_AVAIL ||
causeCode == CallFailCause.TEMPORARY_FAILURE ||
causeCode == CallFailCause.SWITCHING_CONGESTION ||
causeCode == CallFailCause.CHANNEL_NOT_AVAIL ||
causeCode == CallFailCause.QOS_NOT_AVAIL ||
causeCode == CallFailCause.BEARER_NOT_AVAIL ||
causeCode == CallFailCause.ERROR_UNSPECIFIED) {

CellLocation loc = mPhone.getCellLocation();
int cid = -1;
if (loc != null) {
if (isPhoneTypeGsm()) {
cid = ((GsmCellLocation)loc).getCid();
} else {
cid = ((CdmaCellLocation)loc).getBaseStationId();
}
}
EventLog.writeEvent(EventLogTags.CALL_DROP, causeCode, cid,
TelephonyManager.getDefault().getNetworkType());
}

for (int i = 0, s = mDroppedDuringPoll.size(); i < s ; i++) {
GsmCdmaConnection conn = mDroppedDuringPoll.get(i);

conn.onRemoteDisconnect(causeCode, vendorCause);//从远端断开相关处理
}

updatePhoneState();//更新mState状态并发出消息通知

mPhone.notifyPreciseCallStateChanged();
mMetrics.writeRilCallList(mPhone.getPhoneId(), mDroppedDuringPoll);
mDroppedDuringPoll.clear();//清理mDroppedDuringPoll列表
break;

这里这要关注两点

  • 获取causeCode,作为参数调用conn.onRemoteDisconnect方法完成与通话连接从远端断开相关的处理
  • 更新mState并发出通话状态的消息通知

conn.onRemoteDisconnect方法处理逻辑:

/**
* 远端挂断电话的处理
* @param causeCode
* @param vendorCause
*/
/*package*/ void
onRemoteDisconnect(int causeCode, String vendorCause) {
this.mPreciseCause = causeCode;//首先将causeCode通话连接断开原因的编号转变成DisconnectCause的枚举类型,其处理逻辑采用的switch case方式
this.mVendorCause = vendorCause;
onDisconnect(disconnectCauseFromCode(causeCode));
}

首先将cause通话连接断开原因的编号转变成DisconnectCause的枚举类型,其处理逻辑采用了常用的swich case方式 可以查看,这一块代码比较多就不复制在上面了,看馆可以查看GsmCdmaConnection方法中的int disconnectCauseFromCode(int causeCode)方法进行查看对应的关系

结论:

不论是本地主动挂断通话还是远端断开通连接,这里面的差异在于获取通话连接断开的原因,调用conn.onDisconnect来更新conn和mParent(GsmCdmaCall)通话相关信息,最后调用GsmCdmaCallTracker.internalClearDisconnectd()方法去清理所有与通话连接断开相关的信息

本地挂断通话中,首先将对应某一路通话对象GsmCdmaCall的状态修改为DISCONNECTING,同时更新对应的GsmCdmaConnection对象断开通话连接的原因是LOCAL;

而远端断开通话连接中GsmCdmaCall并不会进入DISCONNECTING状态而是直接变为DISCONNECTED状态,对应的GsmCdmaConnection对象断开通话连接的原因可通过RIL查询Modem获取

GsmCdmaCall通话路数 一共不得超过三路通话   ,而GsmCdmaCall与GsmCdmaConnection的关系是什么呢,GsmCdmaCall内部存放着一个GsmCdmaConnection数组,我们可以这样理解 GsmCdmaCall持有多个Connection   而GsmCdmaCall表示一路通话

结语:这里有很多没有讲到因为业务比较大所有  额...  如果有什么问题可以及时提出做讨论,博主挺多通话相关学习资料我可以提供哦   o(* ̄︶ ̄*)o