今天试图解决android挂断电话没有响应的一个bug,跟踪了一下Android 挂断电话流程,在此做个记录
有电话打入是RIL会通知CallNotifier, CallNotifier会调用InCallScreen,这些不再我们今天讨论的范围内,简单提一下。
CallNotifier会调用InCallScreen 代码
PhoneUtils.showIncomingCallUi(mPhone);
然后InCallScreen会显示InCallTouchUI , InCallTouchUI 就是可以拖动接听和挂断的那个界面 ,这个页面持有一个SlidingTab控件,这个就是可以左右拖动的控件了。
InCallTouchUI实现了 SlidingTab.OnTriggerListener, 在该监听者的OnTrigge方法里面处理接听和挂断的动作,代码如下
// // SlidingTab.OnTriggerListener implementation // /** * Handles "Answer" and "Reject" actions for an incoming call. * We get this callback from the SlidingTab * when the user triggers an action. * * To answer or reject the incoming call, we call * InCallScreen.handleOnscreenButtonClick() and pass one of the * special "virtual button" IDs: * - R.id.answerButton to answer the call * or * - R.id.rejectButton to reject the call. */ public void onTrigger(View v, int whichHandle) { log("onDialTrigger(whichHandle = " + whichHandle + ")..."); switch (whichHandle) { case SlidingTab.OnTriggerListener.LEFT_HANDLE: if (DBG) log("LEFT_HANDLE: answer!"); hideIncomingCallWidget(); // ...and also prevent it from reappearing right away. // (This covers up a slow response from the radio; see updateState().) mLastIncomingCallActionTime = SystemClock.uptimeMillis(); // Do the appropriate action. if (mInCallScreen != null) { // Send this to the InCallScreen as a virtual "button click" event: mInCallScreen.handleOnscreenButtonClick(R.id.answerButton); } else { Log.e(LOG_TAG, "answer trigger: mInCallScreen is null"); } break; case SlidingTab.OnTriggerListener.RIGHT_HANDLE: if (DBG) log("RIGHT_HANDLE: reject!"); hideIncomingCallWidget(); // ...and also prevent it from reappearing right away. // (This covers up a slow response from the radio; see updateState().) mLastIncomingCallActionTime = SystemClock.uptimeMillis(); // Do the appropriate action. if (mInCallScreen != null) { // Send this to the InCallScreen as a virtual "button click" event: mInCallScreen.handleOnscreenButtonClick(R.id.rejectButton); //看到了吧,这个就是挂断电话的代码了,继续往下看 } else { Log.e(LOG_TAG, "reject trigger: mInCallScreen is null"); } break; default: Log.e(LOG_TAG, "onDialTrigger: unexpected whichHandle value: " + whichHandle); break; } // Regardless of what action the user did, be sure to clear out // the hint text we were displaying while the user was dragging. mInCallScreen.updateSlidingTabHint(0, 0); }
InCallScreen的 handleOnscreenButtonClick方法
/** * Handles button clicks from the InCallTouchUi widget. */ /* package */ void handleOnscreenButtonClick(int id) { if (DBG) log("handleOnscreenButtonClick(id " + id + ")..."); switch (id) { // TODO: since every button here corresponds to a menu item that we // already handle in onClick(), maybe merge the guts of these two // methods into a separate helper that takes an ID (of either a menu // item *or* touch button) and does the appropriate user action. // Actions while an incoming call is ringing: case R.id.answerButton: internalAnswerCall(); break; case R.id.rejectButton: internalHangupRingingCall(); //挂断电话的方法 break; // The other regular (single-tap) buttons used while in-call: case R.id.holdButton: onHoldClick(); break; case R.id.swapButton: internalSwapCalls(); break; case R.id.endButton: internalHangup(); break; case R.id.dialpadButton: onShowHideDialpad(); break; case R.id.bluetoothButton: onBluetoothClick(); break; case R.id.muteButton: onMuteClick(); break; case R.id.speakerButton: onSpeakerClick(); break; case R.id.addButton: PhoneUtils.startNewCall(mCM); // Fires off an ACTION_DIAL intent break; case R.id.mergeButton: case R.id.cdmaMergeButton: PhoneUtils.mergeCalls(mCM); break; case R.id.manageConferencePhotoButton: // Show the Manage Conference panel. setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE); break; default: Log.w(LOG_TAG, "handleOnscreenButtonClick: unexpected ID " + id); break; } // Just in case the user clicked a "stateful" menu item (i.e. one // of the toggle buttons), we force the in-call buttons to update, // to make sure the user sees the *new* current state. // // (But note that some toggle buttons may *not* immediately change // the state of the Phone, in which case the updateInCallTouchUi() // call here won't have any visible effect. Instead, those // buttons will get updated by the updateScreen() call that gets // triggered when the onPhoneStateChanged() event comes in.) // // TODO: updateInCallTouchUi() is overkill here; it would be // more efficient to update *only* the affected button(s). // Consider adding API for that. (This is lo-pri since // updateInCallTouchUi() is pretty cheap already...) updateInCallTouchUi(); }
/** * Hang up the ringing call (aka "Don't answer"). */ /* package */ void internalHangupRingingCall() { if (DBG) log("internalHangupRingingCall()..."); if (VDBG) PhoneUtils.dumpCallManager(); // In the rare case when multiple calls are ringing, the UI policy // it to always act on the first ringing call.v PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall()); }
static boolean hangupRingingCall(Call ringing) { if (DBG) log("hangup ringing call"); int phoneType = ringing.getPhone().getPhoneType(); if (phoneType == Phone.PHONE_TYPE_CDMA) { // CDMA: Ringing call and Call waiting hangup is handled differently. // For Call waiting we DO NOT call the conventional hangup(call) function // as in CDMA we just want to hungup the Call waiting connection. Call.State state = ringing.getState(); if (state == Call.State.INCOMING) { if (DBG) log("hangup ringing call"); return hangup(ringing); } else if (state == Call.State.WAITING) { if (DBG) log("hangup Call waiting call"); final CallNotifier notifier = PhoneApp.getInstance().notifier; notifier.sendCdmaCallWaitingReject(); return true; } else { // This should never happen cause hangupRingingCall should always be called // if the call.isRinging() returns TRUE, which basically means that the call // should either be in INCOMING or WAITING state if (DBG) log("No Ringing call to hangup"); return false; } } else if ((phoneType == Phone.PHONE_TYPE_GSM) || (phoneType == Phone.PHONE_TYPE_SIP)) { // GSM: Ringing Call and Call waiting, both are hungup by calling // hangup(call) function. if (DBG) log("hangup ringing call"); return hangup(ringing); } else { throw new IllegalStateException("Unexpected phone type: " + phoneType); } }
/** * Trivial wrapper around Call.hangup(), except that we return a * boolean success code rather than throwing CallStateException on * failure. * * @return true if the call was successfully hung up, or false * if the call wasn't actually active. */ static boolean hangup(Call call) { try { call.hangup(); return true; } catch (CallStateException ex) { Log.e(LOG_TAG, "Call hangup: caught " + ex, ex); } return false; }
public void hangup() throws CallStateException { owner.hangup(this); }
然后进入 frameworks/base/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java
可以看到很多不同情况下挂断电话的处理,我们只看第一种情况 hangupWaitingOrBackground
//***** Called from GsmCall /* package */ void hangup (GsmCall call) throws CallStateException { if (call.getConnections().size() == 0) { throw new CallStateException("no connections in call"); } if (call == ringingCall) { if (Phone.DEBUG_PHONE) log("(ringing) hangup waiting or background"); cm.hangupWaitingOrBackground(obtainCompleteMessage()); } else if (call == foregroundCall) { if (call.isDialingOrAlerting()) { if (Phone.DEBUG_PHONE) { log("(foregnd) hangup dialing or alerting..."); } hangup((GsmConnection)(call.getConnections().get(0))); } else { hangupForegroundResumeBackground(); } } else if (call == backgroundCall) { if (ringingCall.isRinging()) { if (Phone.DEBUG_PHONE) { log("hangup all conns in background call"); } hangupAllConnections(call); } else { hangupWaitingOrBackground(); } } else { throw new RuntimeException ("GsmCall " + call + "does not belong to GsmCallTracker " + this); } call.onHangupLocal(); phone.notifyPreciseCallStateChanged(); }
下面进入RIL java, 向rild 发请求了
public void hangupWaitingOrBackground (Message result) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, result); if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)); send(rr); }
总之,android的挂断电话流程还是挺复杂的,中间要处理很多不同的情景。