欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-02-24 09:02:40
...

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

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

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

先处理本地挂断

本地主动挂断通话

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

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

 

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

 

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

 

 

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