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

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

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

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

先处理本地挂断

本地主动挂断通话

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

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

 

Android Telephony 9.0通话挂断连接处理机制(opt/Telephony)_第1张图片

 

接下来开始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 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 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表示一路通话

 

 

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

你可能感兴趣的:(Android Telephony 9.0通话挂断连接处理机制(opt/Telephony))