Android Telephony 9.0通话挂断连接处理机制(opt/Telephony)
前言:今天看了一下通话断开处理流程,所以做一个笔记来记录一下今天的学习成果。
通话断开连接一般有两种应用场景
- 本地主动挂通话
- 远端断开通话连接 (这里还包括网络挂断和对方挂断)
先处理本地挂断
本地主动挂断通话
我们不讲从Dialer点击时流程,直接进入ConnectionService类,ConnectionService类接收到Telecom系统应用发起的hanup()“挂断”,通过Telecom callId匹配TelephonyConnection对象并调用其onDisconnect方法进行挂断请求,以为写博客的 路子 一般先画图进行讲解 ^_^。
本地主动挂断通话流程图:
接下来开始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的区分关系(https://liwangjiang.blog.csdn.net/article/details/103441378)
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 hoConn= " + 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表示一路通话