写在前面的话

本文主要分析Android挂断电话的流程,研究的代码是Android 5.1的,以CDMA为例,GSM同理。
挂断电话主要分两种情况: 本地主动挂断电话 \color{red}{本地主动挂断电话}和 远程断开通话 \color{red}{远程断开通话}


一、 本地主动挂断电话

(如果图片看不清的话,可以右键选择在新标签中打开图片,或者把图片另存到自己电脑再查看。)


这里说的本地主动挂断电话,是指通过点击UI界面上的挂断按钮来挂断电话,而不是通过物理键来挂断电话;至于通过物理键挂断电话,第二节再分析。
挂断电话的按钮floating_end_call_action_button是在call_card_content.xml布局文件里的,该布局文件是在CallCardFragment.java的onCreateView方法里被加载,因此,本地主动挂断电话的流程就从CallCardFragment.java开始。


本文来自 \color{red}{本文来自}http://blog.csdn.net/linyongan ,转载请务必注明出处。 \color{red}{,转载请务必注明出处。}


步骤1:在CallCardFragment.java的onViewCreated()方法里可以找到,挂断电话的按钮floating_end_call_action_button所绑定的监听器,当挂断按钮被点击时就会执行监听器里的方法。

@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);...mFloatingActionButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {getPresenter().endCallClicked();}});...
}

步骤2: CallCardPresenter.java的CallCardPresenter()方法

   public void endCallClicked() {if (mPrimary == null) {return;}Log.i(this, "Disconnecting call: " + mPrimary);mPrimary.setState(Call.State.DISCONNECTING);Call temp = mPrimary;CallList.getInstance().onUpdate(mPrimary);TelecomAdapter.getInstance().disconnectCall(temp.getId());}

在这里做了3件事:
1、把Call的状态设置成DISCONNECTING。(步骤3)
2、更新UI界面,显示正在挂断的界面。(步骤4,5,7,8)
3、继续走挂断电话的流程。(步骤6)

步骤6: TelecomAdapter.java的disconnectCall()方法

    void disconnectCall(String callId) {if (mPhone != null) {getTelecommCallById(callId).disconnect();} else {Log.e(this, "error disconnectCall, mPhone is null");}}private android.telecom.Call getTelecommCallById(String callId) {final Call call = CallList.getInstance().getCallById(callId);return call == null ? null : call.getTelecommCall();}

在getTelecommCallById()方法里通过Package目录下InCallUI Call来间接获取到framework目录下的Telecomm Call,Telecomm Call是在InCallUI Call实例创建时传递进来的。

步骤9: (frameworks\base\telecomm\java\android\telecom目录下)Call.java的disconnect()方法

    /*** Instructs this {@code Call} to disconnect.*/public void disconnect() {mInCallAdapter.disconnectCall(mTelecomCallId);}

mInCallAdapter是InCallAdapter类型的。

步骤10: (frameworks\base\telecomm\java\android\telecom目录下)InCallAdapter.java的disconnectCall()方法

    /*** Instructs Telecom to disconnect the specified call.** @param callId The identifier of the call to disconnect.*/public void disconnectCall(String callId) {try {mAdapter.disconnectCall(callId);} catch (RemoteException e) {}}

mAdapter是IInCallAdapter类型的。

步骤11,12和13: (packages\services\telecomm\src\com\android\server\telecom目录下)InCallAdapter.java的disconnectCall()方法

    @Overridepublic void disconnectCall(String callId) {Log.v(this, "disconnectCall: %s", callId);if (mCallIdMapper.isValidCallId(callId)) {mHandler.obtainMessage(MSG_DISCONNECT_CALL, callId).sendToTarget();}}

在这里通过obtainMessage()方法创建了一个消息类型为MSG_DISCONNECT_CALL的Message,再调用sendToTarget()方法把Message发送出去。

步骤14: InCallAdapter.java内部类InCallAdapterHandler的handleMessage()方法

    private final class InCallAdapterHandler extends Handler {@Overridepublic void handleMessage(Message msg) {Call call;switch (msg.what) {...case MSG_DISCONNECT_CALL:call = mCallIdMapper.getCall(msg.obj);if (call != null) {mCallsManager.disconnectCall(call);} else {Log.w(this, "disconnectCall, unknown call id: %s", msg.obj);}break;...}

步骤15: CallsManager.java的disconnectCall()方法

    /*** Instructs Telecom to disconnect the specified call. Intended to be invoked by the* in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by* the user hitting the end-call button.*/void disconnectCall(Call call) {Log.v(this, "disconnectCall %s", call);if (!mCalls.contains(call)) {Log.w(this, "Unknown call (%s) asked to disconnect", call);} else {mLocallyDisconnectingCalls.add(call);call.disconnect();}}

步骤16和17: (packages\services\telecomm\src\com\android\server\telecom目录下)Call.java的disconnect()方法

   /*** Attempts to disconnect the call through the connection service.*/void disconnect() {disconnectWithReason(DisconnectCause.LOCAL);}void disconnectWithReason(int disconnectCause) {// Track that the call is now locally disconnecting.Log.d(this, "disconnectWithReason dicsonnectcause %d", disconnectCause);setLocallyDisconnecting(true);if (mState == CallState.NEW || mState == CallState.PRE_DIAL_WAIT ||mState == CallState.CONNECTING) {Log.d(this, "Aborting call %s", this);abort();} else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {Log.d(this, "disconnectWithReason %s", mConnectionService);if (mConnectionService == null) {Log.e(this, new Exception(), "disconnect() request on a call without a"+ " connection service.");} else {Log.d(this, "Send disconnect to connection service for call: %s", this);// The call isn't officially disconnected until the connection service// confirms that the call was actually disconnected. Only then is the// association between call and connection service severed, see// {@link CallsManager#markCallAsDisconnected}.mConnectionService.disconnectWithReason(this, disconnectCause);}}}

disconnect()方法里把挂断电话的原因设置成DisconnectCause.LOCAL。还有就是mConnectionService是ConnectionServiceWrapper类型的。

步骤18: ConnectionServiceWrapper.java的disconnectWithReason()方法

    void disconnectWithReason(Call call, int disconnectCause) {final String callId = mCallIdMapper.getCallId(call);if (callId != null && isServiceValid("disconnect")) {try {logOutgoing("disconnect %s", callId);mServiceInterface.disconnectWithReason(callId, disconnectCause);} catch (RemoteException e) {}}}

mServiceInterface是IConnectionService类型的。

步骤19,20和21: ConnectionService.java里mBinder的disconnectWithReason()方法

    private final IBinder mBinder = new IConnectionService.Stub() {...@Overridepublic void disconnectWithReason(String callId, int disconnectCause) {SomeArgs args = SomeArgs.obtain();args.arg1 = callId;args.argi1 = disconnectCause;mHandler.obtainMessage(MSG_DISCONNECT_REASON, args).sendToTarget();}...}

在这里通过obtainMessage()方法创建了一个消息类型为MSG_DISCONNECT_REASON的Message,并且通过sendToTarget()方法把Message发送出去。

步骤22: ConnectionService.java里mHandler的handleMessage()方法

 private final Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {...case MSG_DISCONNECT_REASON: {SomeArgs args = (SomeArgs) msg.obj;try {String callId = (String) args.arg1;int disconnectCause = args.argi1;disconnectWithReason(callId, disconnectCause);} finally {args.recycle();}break;}...}

步骤23: ConnectionService.java的disconnectWithReason()方法

    private void disconnectWithReason(String callId, int disconnectCause) {Log.d(this, "disconnectWithReason %s %d DisconnectCause", callId, disconnectCause);if (mConnectionById.containsKey(callId)) {findConnectionForAction(callId, "disconnect").onDisconnectWithReason(disconnectCause);} else {findConferenceForAction(callId, "disconnect").onDisconnectWithReason(disconnectCause);}}private Connection findConnectionForAction(String callId, String action) {if (mConnectionById.containsKey(callId)) {return mConnectionById.get(callId);}Log.w(this, "%s - Cannot find Connection %s", action, callId);return getNullConnection();}static synchronized Connection getNullConnection() {if (sNullConnection == null) {sNullConnection = new Connection() {};}return sNullConnection;}

通过findConnectionForAction获得一个Connection实例,在这里获得的是TelephonyConnection。

步骤24和25: TelephonyConnection.java的disconnectWithReason()方法

    @Overridepublic void onDisconnectWithReason(int disconnectCause) {Log.v(this, "onDisconnect");hangup(DisconnectCauseUtil.toTelephonyDisconnectCauseCode(disconnectCause));}protected void hangup(int telephonyDisconnectCode) {if (mOriginalConnection != null) {try {// Hanging up a ringing call requires that we invoke call.hangup() as opposed to// connection.hangup(). Without this change, the party originating the call will not// get sent to voicemail if the user opts to reject the call.if (isValidRingingCall()) {Call call = getCall();if (call != null) {call.hangupWithReason(telephonyDisconnectCode);} else {Log.w(this, "Attempting to hangup a connection without backing call.");}} else {// We still prefer to call connection.hangup() for non-ringing calls in order// to support hanging-up specific calls within a conference call. If we invoked// call.hangup() while in a conference, we would end up hanging up the entire// conference call instead of the specific connection.mOriginalConnection.hangupWithReason(telephonyDisconnectCode);}} catch (CallStateException e) {Log.e(this, e, "Call to Connection.hangup failed with exception");}}}

步骤26和27: CDMAConnection.java的hangupWithReason()方法

    @Overridepublic void hangupWithReason(int disconnectCause) throws CallStateException {//TODO not passing disconnect cause in CS.hangup();}@Overridepublic void hangup() throws CallStateException {if (!mDisconnected) {mOwner.hangup(this);} else {throw new CallStateException ("disconnected");}}

注意,此时mOwner.hangup(this);传递的参数是this,也就是CdmaConnection类型的。

步骤28和29: CdmaCallTracker.java的hangup()方法

   //***** Called from CdmaConnection/*package*/ voidhangup (CdmaConnection conn) throws CallStateException {if (conn.mOwner != this) {throw new CallStateException ("CdmaConnection " + conn+ "does not belong to CdmaCallTracker " + this);}if (conn == mPendingMO) {// We're hanging up an outgoing call that doesn't have it's// GSM index assigned yetif (Phone.DEBUG_PHONE) log("hangup: set hangupPendingMO to true");mHangupPendingMO = true;} else if ((conn.getCall() == mRingingCall)&& (mRingingCall.getState() == CdmaCall.State.WAITING)) {// 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;} else {try {mCi.hangupConnection (conn.getCDMAIndex(), obtainCompleteMessage());} catch (CallStateException ex) {// Ignore "connection not found"// Call may have hung up alreadyRlog.w(LOG_TAG,"CdmaCallTracker WARN: hangup() on absent connection "+ conn);}}conn.onHangupLocal();}/*** Obtain a message to use for signalling "invoke getCurrentCalls() when* this operation and all other pending operations are complete*/private MessageobtainCompleteMessage() {return obtainCompleteMessage(EVENT_OPERATION_COMPLETE);}

通过obtainCompleteMessage()创建了一个消息类型为EVENT_OPERATION_COMPLETE的Message作为mCi.hangupConnection的一个参数,其中mCi是RIL.java的实例。

步骤30: RIL.java的hangupConnection()方法

    @Overridepublic voidhangupConnection (int gsmIndex, Message result) {if (RILJ_LOGD) riljLog("hangupConnection: gsmIndex=" + gsmIndex);RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP, result);if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " " +gsmIndex);rr.mParcel.writeInt(1);rr.mParcel.writeInt(gsmIndex);send(rr);}

RILJ封装了一个RIL_REQUEST_HANGUP类型的消息,并且通过send(rr);发送给RILD,RILD继续处理。
步骤31和32: 当RILJ接收到RILD的回应时,在它的processResponse()方法有RIL_REQUEST_HANGUP相关的逻辑处理,最后将消息发送给rr. result对应的handler处理(rr. result就是hangupConnection传递进来的Message参数),即由CdmaCallTracker中的handleMessage()方法进行处理。

步骤33~35: CdmaCallTracker.java的handleMessage()方法

 public voidhandleMessage (Message msg) {...switch (msg.what) {case EVENT_OPERATION_COMPLETE:operationComplete();break;...}private voidoperationComplete() {mPendingOperations--;if (DBG_POLL) log("operationComplete: pendingOperations=" +mPendingOperations + ", needsPoll=" + mNeedsPoll);if (mPendingOperations == 0 && mNeedsPoll) {mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);mCi.getCurrentCalls(mLastRelevantPoll);} else if (mPendingOperations < 0) {// this should never happenRlog.e(LOG_TAG,"CdmaCallTracker.pendingOperations < 0");mPendingOperations = 0;}}

通过obtainMessage()创建了一个消息类型为EVENT_POLL_CALLS_RESULT的Message,然后传递给mCi.getCurrentCalls()

步骤36,37和38:调用RIL.java的getCurrentCalls()方法发出查询Call List状态列表的请求,底层查询完之后返回的结果还是交给CdmaCallTracker中的handleMessage()方法进行处理。

步骤39和40: CdmaCallTracker.java的handleMessage()方法

public voidhandleMessage (Message msg) {...switch (msg.what) {case EVENT_POLL_CALLS_RESULT:{Rlog.d(LOG_TAG, "Event EVENT_POLL_CALLS_RESULT Received");ar = (AsyncResult)msg.obj;if(msg == mLastRelevantPoll) {if(DBG_POLL) log("handle EVENT_POLL_CALL_RESULT: set needsPoll=F");mNeedsPoll = false;mLastRelevantPoll = null;handlePollCalls((AsyncResult)msg.obj);}}break;...}@Overrideprotected voidhandlePollCalls(AsyncResult ar) {// clear the "local hangup" and "missed/rejected call"// cases from the "dropped during poll" list// These cases need no "last call fail" reasonfor (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {CdmaConnection conn = mDroppedDuringPoll.get(i);if (conn.isIncoming() && conn.getConnectTime() == 0) {// Missed or rejected callint 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) {mDroppedDuringPoll.remove(i);hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);}}
}

关于handlePollCalls()方法更详细的讲解请看《handlePollCalls方法详解 》
在步骤16就可以知道电话挂断的原因是DisconnectCause.LOCAL,因此下一步会调用conn.onDisconnect(conn.mCause)

步骤41: CDMAConnection.java的onDisconnect()方法

   /*** Called when the radio indicates the connection has been disconnected.* @param cause call disconnect cause; values are defined in {@link DisconnectCause}*//*package*/ booleanonDisconnect(int cause) {boolean changed = false;mCause = cause;if (!mDisconnected) {doDisconnect();if (VDBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);mOwner.mPhone.notifyDisconnect(this);if (mParent != null) {changed = mParent.connectionDisconnected(this);}}releaseWakeLock();return changed;}

步骤42和43:这里主要通过mPhone.notifyDisconnect(this);通知监听了这个事件的人。
最后发觉是注册TelephonyConnection.java监听了MSG_DISCONNECT事件

getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);

因此,在TelephonyConnection.java里会有MSG_DISCONNECT相应的逻辑处理。

步骤44和45: TelephonyConnection.java里mHandler的handleMessage()方法

 private final Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {...case MSG_DISCONNECT:updateState();break;...}}void updateState() {...if (mOriginalConnectionState != newState) {mOriginalConnectionState = newState;switch (newState) {case IDLE:break;case ACTIVE:setActiveInternal();break;case HOLDING:setOnHold();break;case DIALING:case ALERTING:setDialing();break;case INCOMING:case WAITING:setRinging();break;case DISCONNECTED:if (mSsNotification != null) {setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(mOriginalConnection.getDisconnectCause(),mSsNotification.notificationType,mSsNotification.code));mSsNotification = null;DisconnectCauseUtil.mNotificationCode = 0xFF;DisconnectCauseUtil.mNotificationType = 0xFF;} resetDisconnectCause();close();break;...}}updateConnectionCapabilities();updateAddress();}

当前的State是DISCONNECTED,所以会执行Connection.java的setDisconnected方法;close();也会继续走一段流程,就是把当前Call对象从Call集合里remove掉,这段流程暂且不分析了。

步骤46: TelephonyConnection.java里mHandler的handleMessage()方法

    /*** Sets state to disconnected.** @param disconnectCause The reason for the disconnection, as specified by*         {@link DisconnectCause}.*/public final void setDisconnected(DisconnectCause disconnectCause) {checkImmutable();mDisconnectCause = disconnectCause;setState(STATE_DISCONNECTED);Log.d(this, "Disconnected with cause %s", disconnectCause);for (Listener l : mListeners) {l.onDisconnected(this, disconnectCause);}}

在这里做了两件事
1、把状态设置成STATE_DISCONNECTED。(步骤47)
2、通知绑定了监听器的人电话已挂断。(步骤48,49和50,最后通知了ConnectionServiceWrapper.java)

步骤50~53: ConnectionServiceWrapper.java的setDisconnected方法

        @Overridepublic void setDisconnected(String callId, DisconnectCause disconnectCause) {logIncoming("setDisconnected %s %s", callId, disconnectCause);if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper.isValidConferenceId(callId)) {Log.d(this, "disconnect call %s", callId);SomeArgs args = SomeArgs.obtain();args.arg1 = callId;args.arg2 = disconnectCause;mHandler.obtainMessage(MSG_SET_DISCONNECTED, args).sendToTarget();}}

通过obtainMessage()创建了一个消息类型为MSG_SET_DISCONNECTED的Message,
在mHandler的handleMessage()方法里有相应的处理

步骤54: CallsManager.java的markCallAsDisconnected()方法

    /*** Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the* last live call, then also disconnect from the in-call controller.** @param disconnectCause The disconnect cause, see {@link android.telecomm.DisconnectCause}.*/void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {call.setDisconnectCause(disconnectCause);int prevState = call.getState();setCallState(call, CallState.DISCONNECTED);String activeSub = getActiveSubscription();String conversationSub = getConversationSub();String lchSub = IsAnySubInLch();PhoneAccount phAcc =getPhoneAccountRegistrar().getPhoneAccount(call.getTargetPhoneAccount());if ((call.getTargetPhoneAccount() != null &&call.getTargetPhoneAccount().getId().equals(activeSub)) &&(phAcc != null) && (phAcc.isSet(PhoneAccount.LCH)) &&(conversationSub != null) &&(!conversationSub.equals(activeSub))) {Log.d(this,"Set active sub to conversation sub");setActiveSubscription(conversationSub);} else if ((conversationSub == null) && (lchSub != null) &&((prevState == CallState.CONNECTING) || (prevState == CallState.PRE_DIAL_WAIT)) &&(call.getState() == CallState.DISCONNECTED)) {Log.d(this,"remove sub with call from LCH");updateLchStatus(lchSub);setActiveSubscription(lchSub);manageMSimInCallTones(false);}if ((call.getTargetPhoneAccount() != null) && (phAcc != null) &&(phAcc.isSet(PhoneAccount.LCH))) {Call activecall = getFirstCallWithStateUsingSubId(call.getTargetPhoneAccount().getId(),CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);Log.d(this,"activecall: " + activecall);if (activecall == null) {phAcc.unSetBit(PhoneAccount.LCH);manageMSimInCallTones(false);}}}

步骤55:把状态设置成了DISCONNECTED
步骤56~66:最后通知监听者Call的状态已改变,一直传递到UI界面。最后由CallCardFragment的setCallState方法更新界面,显示已挂断的界面。
步骤67: 底层上报Call的状态已改变的消息。
步骤68: 发出查询Call list状态列表的请求。
步骤69: 底层返回Call list,由CdmaCallTracker进行处理。

主动挂断电话的流程就这么多了。


二、按电源键物理键挂断通话


步骤1: 在PhoneWindowManager.java (frameworks\base\policy\src\com \android\internal\policy\impl)的interceptKeyBeforeQueueing()里有对物理键的相关事件处理。

         case KeyEvent.KEYCODE_POWER: {result &= ~ACTION_PASS_TO_USER;isWakeKey = false; // wake-up will be handled separatelyif (down) {boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,event.getDownTime(), isImmersiveMode(mLastSystemUiFlags));if (panic) {mHandler.post(mRequestTransientNav);}if (interactive && !mPowerKeyTriggered&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {mPowerKeyTriggered = true;mPowerKeyTime = event.getDownTime();interceptScreenshotChord();interceptScreenshotLog();if (mButtonLightEnabled) {try {mLight.setButtonLightEnabled(false);} catch(RemoteException e) {Slog.e(TAG, "remote call for turn off button light failed.");}}}TelecomManager telecomManager = getTelecommService();boolean hungUp = false;if (telecomManager != null) {if (telecomManager.isRinging()) {// Pressing Power while there's a ringing incoming// call should silence the ringer.telecomManager.silenceRinger();} else if ((mIncallPowerBehavior& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0&& telecomManager.isInCall() && interactive) {// Otherwise, if "Power button ends call" is enabled,// the Power button will hang up any current active call.hungUp = telecomManager.endCall();}  else {mContext.sendBroadcast(new Intent(POWER_KEYDOWN_MUTE));}}interceptPowerKeyDown(event, interactive);} else {interceptPowerKeyUp(event, interactive, canceled);}break;}

如果正在通话中,电源键被点击,则说明用户想挂断电话(用户开了辅助功能的话),则会调用telecomManager.endCall()来挂断电话。

步骤2: TelecomManager.java的endCall()方法

    /*** Ends an ongoing call.* TODO: L-release - need to convert all invocations of ITelecomService#endCall to use this* method (clockwork & gearhead).* @hide*/@SystemApipublic boolean endCall() {try {if (isServiceConnected()) {return getTelecomService().endCall();}} catch (RemoteException e) {Log.e(TAG, "Error calling ITelecomService#endCall", e);}return false;}

在这里通过getTelecomService()方法获取到ITelecomService接口,远程调用TelecomService.java的endCall()方法

步骤3~5: TelecomService.java的endCall()方法

   /*** @see android.telecom.TelecomManager#endCall*/@Overridepublic boolean endCall() {enforceModifyPermission();return (boolean) sendRequest(MSG_END_CALL);}/*** Posts the specified command to be executed on the main thread, waits for the request to* complete, and returns the result.*/private Object sendRequest(int command) {if (Looper.myLooper() == mMainThreadHandler.getLooper()) {MainThreadRequest request = new MainThreadRequest();mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));return request.result;} else {MainThreadRequest request = sendRequestAsync(command, 0);// Wait for the request to completesynchronized (request) {while (request.result == null) {try {request.wait();} catch (InterruptedException e) {// Do nothing, go back and wait until the request is complete}}}return request.result;}}

在这里通过obtainMessage()方法创建了一个消息类型为MSG_END_CALL的Message,然后传递给mMainThreadHandler.handleMessage()方法,让它处理这个Message。

步骤6~8: TelecomService.java的endCall()方法

private final class MainThreadHandler extends Handler {@Overridepublic void handleMessage(Message msg) {if (msg.obj instanceof MainThreadRequest) {MainThreadRequest request = (MainThreadRequest) msg.obj;Object result = null;switch (msg.what) {...case MSG_END_CALL:result = endCallInternal();break;...}private boolean endCallInternal() {// Always operate on the foreground call if one exists, otherwise get the first call in// priority order by call-state.Call call = mCallsManager.getForegroundCall();if (call == null) {call = mCallsManager.getFirstCallWithState(CallState.ACTIVE,CallState.DIALING,CallState.RINGING,CallState.ON_HOLD);}if (call != null) {if (call.getState() == CallState.RINGING) {call.reject(false /* rejectWithMessage */, null);} else {call.disconnect();}return true;}return false;}

MSG_END_CALL消息是由endCallInternal()来处理,如果当前正在响铃的话就执行拒接电话的流程;否则的话,就调用call.disconnect()执行挂断电话的流程。这里的Call.java是在packages\services\telecomm\src\com\android\server\telecom目录下的。

步骤9:之后的流程跟点击挂断按钮方式的挂断流程步骤16~69相同,在这里就不重复了


三、远程挂断通话


RILJ收到Call状态变化通知RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,接着通知CdmaCallTracker,CdmaCallTracker则在pollCallsWhenSafe方法里调用RILJ的getCurrentCalls方法请求查询Call状态列表,底层处理完之后发出通知,CdmaCallTracker在handleMessage里进行响应,然后进入handlePollCalls方法里


1. CdmaCallTracker.java的handlePollCalls()方法

上面的流程跟接电话的流程都是有很多相似的,所以这里一笔带过,重点关注CdmaCallTracker的 handlePollCalls \color{red}{handlePollCalls}方法

protected voidhandlePollCalls(AsyncResult ar){...if (mDroppedDuringPoll.size() > 0) {mCi.getLastCallFailCause(obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));}...
}

2. RIL.java的getLastCallFailCause()方法

在这里会先调用 RIL的getLastCallFailCause \color{red}{RIL的getLastCallFailCause}方法向RIL查询 通话断开的原因 \color{red}{通话断开的原因},注意这里传进来了一个消息类型为EVENT_GET_LAST_CALL_FAIL_CAUSE的Message

public voidgetLastCallFailCause (Message result) {RILRequest rr= RILRequest.obtain(RIL_REQUEST_LAST_CALL_FAIL_CAUSE, result);if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));send(rr);}

对应的log是

08-04 13:22:45.842 D/RILJ    ( 3034): [5549]> LAST_CALL_FAIL_CAUSE

3. RIL.java的processSolicited()方法

RIL向底层发送了请求,底层处理完成之后,就会把处理的结果返回给RIL, RIL.java在processSolicited \color{red}{RIL.java在processSolicited}方法里处理

private RILRequest processSolicited (Parcel p) {...case RIL_REQUEST_LAST_CALL_FAIL_CAUSE: ret =  responseInts(p); break;...//打印log日志if (RILJ_LOGD) riljLog(rr.serialString() + "< " + requestToString(rr.mRequest)+ " " + retToString(rr.mRequest, ret));if (rr.mResult != null) {AsyncResult.forMessage(rr.mResult, null, tr);rr.mResult.sendToTarget();//发出handler消息通知
}

对应的log是

08-04 13:22:45.847 D/RILJ    ( 3034): [5549]< LAST_CALL_FAIL_CAUSE {16}

4. CdmaCallTracker.java的handleMessage()方法

rr.mResult.sendToTarget()发出handler消息通知后,会在 CdmaCallTracker中的handleMessage \color{red}{CdmaCallTracker中的handleMessage}方法中响应。

public voidhandleMessage (Message msg) {...switch (msg.what) {case EVENT_GET_LAST_CALL_FAIL_CAUSE:int causeCode;ar = (AsyncResult)msg.obj;operationComplete();if (ar.exception != null) {// 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 {causeCode = ((int[])ar.result)[0];}for (int i = 0, s =  mDroppedDuringPoll.size(); i < s ; i++) {CdmaConnection conn = mDroppedDuringPoll.get(i);conn.onRemoteDisconnect(causeCode);}//更新phone状态updatePhoneState();mPhone.notifyPreciseCallStateChanged();//清除断开的通话连接mDroppedDuringPoll.clear();break;...
}

conn.onRemoteDisconnect(causeCode); \color{red}{conn.onRemoteDisconnect(causeCode);}通话断开的原因就是causeCode,conn是CdmaConnection类型的,下一步进入CdmaConnection.java


5. CdmaConnection.java的onRemoteDisconnect()方法

void onRemoteDisconnect(int causeCode) {          onDisconnect(disconnectCauseFromCode(causeCode));
}/** Called when the radio indicates the connection has been disconnected */
boolean onDisconnect(DisconnectCause cause) {boolean changed = false;mCause = cause;if (!mDisconnected) {doDisconnect();if (VDBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);mOwner.mPhone.notifyDisconnect(this);if (mParent != null) {changed = mParent.connectionDisconnected(this);}}releaseWakeLock();return changed;
}

看到这一行mPhone.notifyDisconnect(this); 因为我们这里研究的是CDMA,所以mPhone就是PhoneBase的其中一个子类CDMAPhone


6. CDMAPhone.java的notifyDisconnect()方法

void notifyDisconnect(Connection cn) {mDisconnectRegistrants.notifyResult(cn);
}

我们要找出mDisconnectRegistrants被调用的地方,我们先来到CallManager.java的registerForDisconnect方法里,继续找,我们要找的是“phone.registerForDisconnect”,在CallManager.java找到下面这行:

phone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null);

可以知道,是CallManager.java监听了EVENT_DISCONNECT事件,所以会在它的handleMessage方法里响应这个消息


7. CallManager.java的handleMessage方法

public void handleMessage(Message msg) {switch (msg.what) {case EVENT_DISCONNECT:if (VDBG) Rlog.d(LOG_TAG, " handleMessage (EVENT_DISCONNECT)");
mDisconnectRegistrants.notifyRegistrants((AsyncResult) msg.obj);break;...
}

又是mDisconnectRegistrants,还是要找它的被调用的地方,不过这次要找的是“callManager.registerForDisconnect”,在CallStateMonitor.java里找到这行:

callManager.registerForDisconnect(this, PHONE_DISCONNECT, null);

这里有个要注意的地方,CallStateMonitor并不是Handler的子类,所以它没有handleMessage方法,那么它监听了PHONE_DISCONNECT事件,在哪里响应并且处理它呢,我们要找PHONE_DISCONNECT被调用的地方,在CallNotifier.java的handleMessage方法里找到了

public void handleMessage(Message msg) {switch (msg.what) {...case CallStateMonitor.PHONE_DISCONNECT:if (DBG) log("DISCONNECT");onDisconnect((AsyncResult) msg.obj);break;...
}

所以CallNotifier会响应PHONE_DISCONNECT事件。


最后,贴出上面分析的远程挂断通话流程的log

08-04 13:22:45.816 D/RILJ    ( 3034):[UNSL]< UNSOL_RESPONSE_CALL_STATE_CHANGED
08-04 13:22:45.816 D/RILJ    ( 3034): [5548]> GET_CURRENT_CALLS
08-04 13:22:45.832 D/RILJ    ( 3034): [5548]< GET_CURRENT_CALLS
08-04 13:22:45.842 D/RILJ    ( 3034): [5549]> LAST_CALL_FAIL_CAUSE
08-04 13:22:45.845 D/CdmaCallTracker( 3034):
[CdmaCallTracker] update phone state, old=OFFHOOK new=OFFHOOK
08-04 13:22:45.847 D/RILJ    ( 3034): [5549]< LAST_CALL_FAIL_CAUSE {16}
08-04 13:22:45.857 D/CdmaCallTracker( 3034):
[CdmaCallTracker] update phone state, old=OFFHOOK new=IDLE
08-04 13:22:46.361 D/CdmaCallTracker( 3034):
[CdmaCallTracker] update phone state, old=IDLE new=IDLE
08-04 13:22:46.469 D/CallNotifier( 3034): DISCONNECT
08-04 13:22:46.469 D/CallNotifier( 3034): onDisconnect()...  CallManager state: IDLE
08-04 13:22:46.470 D/CallNotifier( 3034):
onDisconnect: cause = NORMAL, incoming = true, date = 1438665738497
08-04 13:22:46.502 D/CallNotifier( 3034):
stopSignalInfoTone: Stopping SignalInfo tone player
08-04 13:22:46.504 D/CallNotifier( 3034): stopRing()... (onDisconnect)

Android 5.1 Phone 挂断电话流程分析相关推荐

  1. 远程挂断电话流程分析

    3,远程挂断电话流程分析 3.1 services Telephony 当远程挂断/拒接电话时,GsmCallTracker的handlePollCalls 方法有关代码如下, if (mDroppe ...

  2. Android 7.0 挂断电话流程分析

    1.图形显示 挂断电话分为本地挂断和远程对方挂断 2.本地挂断 1).点击按钮 先看按键的监听事件 CallCardFragment.java 中有对按钮的监听事件 @Overridepublic v ...

  3. android6.0 挂断电话流程分析(一)

    下面是android 6.0挂断电话的流程分析图: 后继续更新挂断回调...........................!

  4. Android挂断电话流程

    近期在友盟上看到许多关于挂断电话导致崩溃的问题,如下异常 java.lang.NoSuchMethodError: No interface method endCall()Z in class Lc ...

  5. Android自动接听和挂断电话实现原理

    转自:http://bbs.51cto.com/thread-1078059-1.html 一  前言 这两天要研究类似白名单黑名单以及手势自动接听的一些功能,所以呢,自然而然的涉及到怎么自动接听/挂 ...

  6. android蓝牙耳机来电铃声,Android蓝牙耳机接听挂断电话流程

    一.alps/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java image.png proc ...

  7. Android高版本无法挂断电话问题

    今天接到一个之前的功能,现在要拿出来看一看能不能用.打电话接到挂断,在网上搜了半天,自己的代码看了半天也就两种. 第一种 try {Method method = Class.forName(&quo ...

  8. android挂断电话广播,android实现接通和挂断电话

    android实现接通和挂断电话 发布时间:2020-08-21 01:52:02 来源:脚本之家 阅读:230 作者:WillenWu 本文实例为大家分享了android实现接通和挂断电话的具体代码 ...

  9. 挂断电话的实现(即类似于电话号码黑名单)

    在文章的开头先列出以下需要注意的地方: 1.需要用到的权限如下: <uses-permission android:name="android.permission.READ_PHON ...

最新文章

  1. 【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记20 Multiple MVCs 多MVC模式、NavigationController导航控制器...
  2. 哥伦比亚大学计算机工程面试题
  3. linux内核组件分析之--设备驱动模型之bus
  4. 【渝粤题库】陕西师范大学400011 思想政治教育学科教学论 作业(专升本)
  5. 云服务器安装操作系统后如何连接,服务器如何安装操作系统
  6. React开发(238):dva概念3action
  7. ibm量子计算机科学家,量子计算机比传统计算机更具优势?IBM科学家这样说……...
  8. 信息收集--IP扫描 (上篇)
  9. python笔记02_面向对象编程和面向对象高级编程
  10. 用户配置文件同步服务,显示为正在启动解决办法
  11. oracle into 循环,oracle游标中使用select into查询结果为NULL导致异常提前退出循环——菜鸟解决办法(^_^)...
  12. iOS 将本地项目/demo上传到github的简单方法
  13. 字符串常量池、堆、栈
  14. 小程序使用javascript-obfuscator工具进行代码混淆处理
  15. pwn|软件安全相关问题学习笔记
  16. Python -- 7. 函数
  17. 2021“设计+”珠宝首饰创新设计论坛
  18. 前端构建工具Gulp的学习和使用
  19. 基于JAVASE的彩票摇号系统
  20. 字符串——OKR-Periods of Words(kmp求最短相同前后缀或者说求最长循环节)

热门文章

  1. android 华为荣耀v8不能上传视频,华为荣耀V8支持什么视频格式
  2. countUp.js在react中的应用
  3. 思科Telnet远程登录
  4. 菜鸟流程-Touching App(2)- 设置界面
  5. php yaf 2.3.5,Yaf 3.2 发布
  6. 蝙蝠算法的matlab程序,经典蝙蝠算法MATLAB实现
  7. 从双非文科生到年薪50万大厂女码农,我用了3年
  8. ShellCode_Loader - MsfCobaltStrike免杀ShellCode加载器加密工具
  9. Hi3861开发环境搭建 ||避坑指南|| [适用于几乎所有以Hi3861为主控的开发板]
  10. identityserver4 Authorize 传参