Android 5.1 Phone 挂断电话流程分析
写在前面的话
本文主要分析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 挂断电话流程分析相关推荐
- 远程挂断电话流程分析
3,远程挂断电话流程分析 3.1 services Telephony 当远程挂断/拒接电话时,GsmCallTracker的handlePollCalls 方法有关代码如下, if (mDroppe ...
- Android 7.0 挂断电话流程分析
1.图形显示 挂断电话分为本地挂断和远程对方挂断 2.本地挂断 1).点击按钮 先看按键的监听事件 CallCardFragment.java 中有对按钮的监听事件 @Overridepublic v ...
- android6.0 挂断电话流程分析(一)
下面是android 6.0挂断电话的流程分析图: 后继续更新挂断回调...........................!
- Android挂断电话流程
近期在友盟上看到许多关于挂断电话导致崩溃的问题,如下异常 java.lang.NoSuchMethodError: No interface method endCall()Z in class Lc ...
- Android自动接听和挂断电话实现原理
转自:http://bbs.51cto.com/thread-1078059-1.html 一 前言 这两天要研究类似白名单黑名单以及手势自动接听的一些功能,所以呢,自然而然的涉及到怎么自动接听/挂 ...
- android蓝牙耳机来电铃声,Android蓝牙耳机接听挂断电话流程
一.alps/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java image.png proc ...
- Android高版本无法挂断电话问题
今天接到一个之前的功能,现在要拿出来看一看能不能用.打电话接到挂断,在网上搜了半天,自己的代码看了半天也就两种. 第一种 try {Method method = Class.forName(&quo ...
- android挂断电话广播,android实现接通和挂断电话
android实现接通和挂断电话 发布时间:2020-08-21 01:52:02 来源:脚本之家 阅读:230 作者:WillenWu 本文实例为大家分享了android实现接通和挂断电话的具体代码 ...
- 挂断电话的实现(即类似于电话号码黑名单)
在文章的开头先列出以下需要注意的地方: 1.需要用到的权限如下: <uses-permission android:name="android.permission.READ_PHON ...
最新文章
- 【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记20 Multiple MVCs 多MVC模式、NavigationController导航控制器...
- 哥伦比亚大学计算机工程面试题
- linux内核组件分析之--设备驱动模型之bus
- 【渝粤题库】陕西师范大学400011 思想政治教育学科教学论 作业(专升本)
- 云服务器安装操作系统后如何连接,服务器如何安装操作系统
- React开发(238):dva概念3action
- ibm量子计算机科学家,量子计算机比传统计算机更具优势?IBM科学家这样说……...
- 信息收集--IP扫描 (上篇)
- python笔记02_面向对象编程和面向对象高级编程
- 用户配置文件同步服务,显示为正在启动解决办法
- oracle into 循环,oracle游标中使用select into查询结果为NULL导致异常提前退出循环——菜鸟解决办法(^_^)...
- iOS 将本地项目/demo上传到github的简单方法
- 字符串常量池、堆、栈
- 小程序使用javascript-obfuscator工具进行代码混淆处理
- pwn|软件安全相关问题学习笔记
- Python -- 7. 函数
- 2021“设计+”珠宝首饰创新设计论坛
- 前端构建工具Gulp的学习和使用
- 基于JAVASE的彩票摇号系统
- 字符串——OKR-Periods of Words(kmp求最短相同前后缀或者说求最长循环节)
热门文章
- android 华为荣耀v8不能上传视频,华为荣耀V8支持什么视频格式
- countUp.js在react中的应用
- 思科Telnet远程登录
- 菜鸟流程-Touching App(2)- 设置界面
- php yaf 2.3.5,Yaf 3.2 发布
- 蝙蝠算法的matlab程序,经典蝙蝠算法MATLAB实现
- 从双非文科生到年薪50万大厂女码农,我用了3年
- ShellCode_Loader - MsfCobaltStrike免杀ShellCode加载器加密工具
- Hi3861开发环境搭建 ||避坑指南|| [适用于几乎所有以Hi3861为主控的开发板]
- identityserver4 Authorize 传参