Android 5.1 Phone 挂断电话流程分析
2015-08-29 20:36
525 查看
写在前面的话
本文主要分析Android挂断电话的流程,研究的代码是Android 5.1的,以CDMA为例,GSM同理。挂断电话主要分两种情况:本地主动挂断电话和远程断开通话
一、 本地主动挂断电话
(如果图片看不清的话,可以右键选择在新标签中打开图片,或者把图片另存到自己电脑再查看。)
这里说的本地主动挂断电话,是指通过点击UI界面上的挂断按钮来挂断电话,而不是通过物理键来挂断电话;至于通过物理键挂断电话,第二节再分析。
挂断电话的按钮
floating_end_call_action_button是在
call_card_content.xml布局文件里的,该布局文件是在CallCardFragment.java的onCreateView方法里被加载,因此,本地主动挂断电话的流程就从CallCardFragment.java开始。
本文来自http://blog.csdn.net/linyongan ,转载请务必注明出处。
步骤1:在CallCardFragment.java的onViewCreated()方法里可以找到,挂断电话的按钮
floating_end_call_action_button所绑定的监听器,当挂断按钮被点击时就会执行监听器里的方法。
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ... mFloatingActionButton.setOnClickListener(new View.OnClickListener() { @Override public 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()方法
@Override public 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 { @Override public 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() { ... @Override public 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()) { @Override public 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()方法
@Override public 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()方法
@Override public void hangupWithReason(int disconnectCause) throws CallStateException { //TODO not passing disconnect cause in CS. hangup(); } @Override public 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*/ void hangup (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 yet if (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 already Rlog.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 Message obtainCompleteMessage() { return obtainCompleteMessage(EVENT_OPERATION_COMPLETE); }
通过obtainCompleteMessage()创建了一个消息类型为
EVENT_OPERATION_COMPLETE的Message作为
mCi.hangupConnection的一个参数,其中mCi是RIL.java的实例。
步骤30: RIL.java的hangupConnection()方法
@Override public void hangupConnection (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 void handleMessage (Message msg) { ... switch (msg.what) { case EVENT_OPERATION_COMPLETE: operationComplete(); break; ... } private void operationComplete() { 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 happen Rlog.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 void handleMessage (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; ... } @Override protected void handlePollCalls(AsyncResult ar) { // clear the "local hangup" and "missed/rejected call" // cases from the "dropped during poll" list // These cases need no "last call fail" reason for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) { CdmaConnection conn = mDroppedDuringPoll.get(i); 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); 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*/ boolean onDisconnect(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() { @Override public 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方法
@Override public 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 separately if (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 */ @SystemApi public 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 */ @Override public 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 complete synchronized (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 { @Override public 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方法protected void handlePollCalls(AsyncResult ar){ ... if (mDroppedDuringPoll.size() > 0) { mCi.getLastCallFailCause( obtainNoPollCompleteMessage( EVENT_GET_LAST_CALL_FAIL_CAUSE)); } ... }
2. RIL.java的getLastCallFailCause()方法
在这里会先调用RIL的getLastCallFailCause方法向RIL查询通话断开的原因,注意这里传进来了一个消息类型为EVENT_GET_LAST_CALL_FAIL_CAUSE的Messagepublic void getLastCallFailCause (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方法里处理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方法中响应。public void handleMessage (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);通话断开的原因就是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)
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories