今天试图解决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;
}
然后进入 GsmCall
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);
}
再往下就是 rild , 厂商refence ril工作了 ,其实就是发AT命令,这部分每个厂商可能有自己的实现,就不细说了 。
总之,android的挂断电话流程还是挺复杂的,中间要处理很多不同的情景。