InCallScreen处理来电和拨号的界面,接通电话也是这个界面,接下来分析InCallScreen类是如何处理拨号流程的;
@Override
protected void onCreate(Bundle icicle) {
Log.i(LOG_TAG, "onCreate()... this = " + this);
Profiler.callScreenOnCreate();
super.onCreate(icicle);
// Make sure this is a voice-capable device.
if (!PhoneApp.sVoiceCapable) {
// There should be no way to ever reach the InCallScreen on a
// non-voice-capable device, since this activity is not exported by
// our manifest, and we explicitly disable any other external APIs
// like the CALL intent and ITelephony.showCallScreen().
// So the fact that we got here indicates a phone app bug.
Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");
finish();
return;
}
// 获取PhoneApp实例
mApp = PhoneApp.getInstance();
// 设置通话界面
mApp.setInCallScreenInstance(this);
// 添加这个标记可以让Activity显示在锁屏的上方
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
if (mApp.getPhoneState() == Phone.State.OFFHOOK) {
// While we are in call, the in-call screen should dismiss the
// keyguard.
// This allows the user to press Home to go directly home without
// going through
// an insecure lock screen.
// But we do not want to do this if there is no active call so we do
// not
// bypass the keyguard if the call is not answered or declined.
// 解除锁屏。只有锁屏界面不是加密的才能解锁。如果锁屏界面是加密的,那么用户解锁之后才能看到此窗口,除非设置了FLAG_SHOW_WHEN_LOCKED选项。
flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
}
getWindow().addFlags(flags);
// Also put the system bar (if present on this device) into
// "lights out" mode any time we're the foreground activity.
WindowManager.LayoutParams params = getWindow().getAttributes();
params.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE;
getWindow().setAttributes(params);
setPhone(mApp.phone); // Sets mPhone
mCM = mApp.mCM;
log("- onCreate: phone state = " + mCM.getState());
mBluetoothHandsfree = mApp.getBluetoothHandsfree();
if (VDBG)
log("- mBluetoothHandsfree: " + mBluetoothHandsfree);
if (mBluetoothHandsfree != null) {
// The PhoneApp only creates a BluetoothHandsfree instance in the
// first place if BluetoothAdapter.getDefaultAdapter()
// succeeds. So at this point we know the device is BT-capable.
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAdapter.getProfileProxy(getApplicationContext(),
mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
}
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Inflate everything in incall_screen.xml and add it to the screen.
setContentView(R.layout.incall_screen);
// 初始化CallCard以及InCallTouchUi等截面
initInCallScreen();
// 注册关于Phone状态改变的监听事件,这也就是为什么Phone状态改变之后InCallScreen能够收到变化消息的原因,这一点我们在来电流程中也有提及;
registerForPhoneStates();
// No need to change wake state here; that happens in onResume() when we
// are actually displayed.
// Handle the Intent we were launched with, but only if this is the
// the very first time we're being launched (ie. NOT if we're being
// re-initialized after previously being shut down.)
// Once we're up and running, any future Intents we need
// to handle will come in via the onNewIntent() method.
if (icicle == null) {
if (DBG)
log("onCreate(): this is our very first launch, checking intent...");
// 该方法用于处理InCallScreen收到的Intent信息
internalResolveIntent(getIntent());
}
Profiler.callScreenCreated();
if (DBG)
log("onCreate(): exit");
}
只要分析三个函数:initInCallScreen、registerForPhoneStates、internalResolveIntent
private void initInCallScreen() {
if (VDBG)
log("initInCallScreen()...");
// Have the WindowManager filter out touch events that are "too fat".
getWindow().addFlags(
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
mInCallPanel = (ViewGroup) findViewById(R.id.inCallPanel);
// Initialize the CallCard.
mCallCard = (CallCard) findViewById(R.id.callCard);
if (VDBG)
log(" - mCallCard = " + mCallCard);
mCallCard.setInCallScreenInstance(this);
//初始化界面的UI布局
initInCallTouchUi();
// 助手类跟踪enabledness / UI控件的状态
mInCallControlState = new InCallControlState(this, mCM);
//助手类运行“Manage conference”的用户界面
mManageConferenceUtils = new ManageConferenceUtils(this, mCM);
// The DTMF Dialpad.
// TODO: Don't inflate this until the first time it's needed.
ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
stub.inflate();
//DTMF拨号盘初始化
mDialerView = (DTMFTwelveKeyDialerView) findViewById(R.id.dtmf_twelve_key_dialer_view);
if (DBG)
log("- Found dialerView: " + mDialerView);
// Sanity-check that (regardless of the device) at least the
// dialer view is present:
if (mDialerView == null) {
Log.e(LOG_TAG, "onCreate: couldn't find dialerView",
new IllegalStateException());
}
//创建DTMFTwelveKeyDialer实例
mDialer = new DTMFTwelveKeyDialer(this, mDialerView);
}
以下函数是通过CallManager类向Framework层注册一些状态,只要Framework层的状态改变就会通知上层应用修改UI;如果是来电就会在Handler收到
PHONE_INCOMING_RING标记。实际上为观察者模式的运用
private void registerForPhoneStates() {
if (!mRegisteredForPhoneStates) {
mCM.registerForPreciseCallStateChanged(mHandler,
PHONE_STATE_CHANGED, null);
mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
mCM.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
// register for the MMI complete message. Upon completion,
// PhoneUtils will bring up a system dialog instead of the
// message display class in PhoneUtils.displayMMIComplete().
// We'll listen for that message too, so that we can finish
// the activity at the same time.
mCM.registerForMmiComplete(mHandler, PhoneApp.MMI_COMPLETE, null);
mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED,
null);
mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);
mCM.registerForNewRingingConnection(mHandler,
PHONE_NEW_RINGING_CONNECTION, null);
mRegisteredForPhoneStates = true;
}
}
internalResolveIntent是恢复Activity保存的状态
private void internalResolveIntent(Intent intent) {
if (intent == null || intent.getAction() == null) {
return;
}
String action = intent.getAction();
if (DBG)
log("internalResolveIntent: action=" + action);
// In gingerbread and earlier releases, the InCallScreen used to
// directly handle certain intent actions that could initiate phone
// calls, namely ACTION_CALL and ACTION_CALL_EMERGENCY, and also
// OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING.
//
// But it doesn't make sense to tie those actions to the InCallScreen
// (or especially to the *activity lifecycle* of the InCallScreen).
// Instead, the InCallScreen should only be concerned with running the
// onscreen UI while in a call. So we've now offloaded the call-control
// functionality to a new module called CallController, and OTASP calls
// are now launched from the OtaUtils startInteractiveOtasp() or
// startNonInteractiveOtasp() methods.
//
// So now, the InCallScreen is only ever launched using the ACTION_MAIN
// action, and (upon launch) performs no functionality other than
// displaying the UI in a state that matches the current telephony
// state.
if (action.equals(intent.ACTION_MAIN)) {
// This action is the normal way to bring up the in-call UI.
//
// Most of the interesting work of updating the onscreen UI (to
// match the current telephony state) happens in the
// syncWithPhoneState() => updateScreen() sequence that happens in
// onResume().
//
// But we do check here for one extra that can come along with the
// ACTION_MAIN intent:
if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
// SHOW_DIALPAD_EXTRA can be used here to specify whether the
// DTMF
// dialpad should be initially visible. If the extra isn't
// present at all, we just leave the dialpad in its previous
// state.
boolean showDialpad = intent.getBooleanExtra(
SHOW_DIALPAD_EXTRA, false);
if (VDBG)
log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: "
+ showDialpad);
// If SHOW_DIALPAD_EXTRA is specified, that overrides whatever
// the previous state of inCallUiState.showDialpad was.
mApp.inCallUiState.showDialpad = showDialpad;
}
// ...and in onResume() we'll update the onscreen dialpad state to
// match the InCallUiState.
return;
}
if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {
// Bring up the in-call UI in the OTASP-specific "activate" state;
// see OtaUtils.startInteractiveOtasp(). Note that at this point
// the OTASP call has not been started yet; we won't actually make
// the call until the user presses the "Activate" button.
if (!TelephonyCapabilities.supportsOtasp(mPhone)) {
throw new IllegalStateException(
"Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "
+ intent);
}
setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
if ((mApp.cdmaOtaProvisionData != null)
&& (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
mApp.cdmaOtaScreenState.otaScreenState = CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
}
return;
}
// Various intent actions that should no longer come here directly:
if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
// This intent is now handled by the InCallScreenShowActivation
// activity, which translates it into a call to
// OtaUtils.startInteractiveOtasp().
throw new IllegalStateException(
"Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "
+ intent);
} else if (action.equals(Intent.ACTION_CALL)
|| action.equals(Intent.ACTION_CALL_EMERGENCY)) {
// ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now
// translates them into CallController.placeCall() calls rather than
// launching the InCallScreen directly.
throw new IllegalStateException(
"Unexpected CALL action received by InCallScreen: "
+ intent);
} else if (action.equals(ACTION_UNDEFINED)) {
// This action is only used for internal bookkeeping; we should
// never actually get launched with it.
Log.wtf(LOG_TAG,
"internalResolveIntent: got launched with ACTION_UNDEFINED");
return;
} else {
Log.wtf(LOG_TAG,
"internalResolveIntent: unexpected intent action: "
+ action);
// But continue the best we can (basically treating this case
// like ACTION_MAIN...)
return;
}
}
拨号到界面显示出来到此就分析完了,但没有涉及到Framework层,后面会再分析Framework;在手机端挂断电话后,拨号界面也会关闭,那么这个过程是怎么走的,下面也来分析下在前面一篇界面了PhoneApp类的初始化,在onCreate()时会对CallNotifier类进行初始化
// Create the CallNotifer singleton, which handles
// asynchronous events from the telephony layer (like
// launching the incoming-call UI when an incoming call comes
// in.)
notifier = CallNotifier.init(this, phone, ringer, mBtHandsfree,
new CallLogAsync());
创建CallNotifier,使用单例
/**
* Initialize the singleton CallNotifier instance.
* This is only done once, at startup, from PhoneApp.onCreate().
*/
/* package */ static CallNotifier init(PhoneApp app, Phone phone, Ringer ringer,
BluetoothHandsfree btMgr, CallLogAsync callLog) {
synchronized (CallNotifier.class) {
if (sInstance == null) {
sInstance = new CallNotifier(app, phone, ringer, btMgr, callLog);
} else {
Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
}
return sInstance;
}
}
在构造函数里做一些初始化工作
/** Private constructor; @see init() */
private CallNotifier(PhoneApp app, Phone phone, Ringer ringer,
BluetoothHandsfree btMgr, CallLogAsync callLog) {
mApplication = app;
mCM = app.mCM;
mCallLog = callLog;
mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
//跟CallManager注册通知,跟Framework通訊
registerForNotifications();
// Instantiate the ToneGenerator for SignalInfo and CallWaiting
// TODO: We probably don't need the mSignalInfoToneGenerator instance
// around forever. Need to change it so as to create a ToneGenerator instance only
// when a tone is being played and releases it after its done playing.
try {
mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
TONE_RELATIVE_VOLUME_SIGNALINFO);
} catch (RuntimeException e) {
Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
"mSignalInfoToneGenerator: " + e);
mSignalInfoToneGenerator = null;
}
mRinger = ringer;
mBluetoothHandsfree = btMgr;
TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
| PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
}
注册消息,跟Framework层通讯
private void registerForNotifications() {
mCM.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
mCM.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);
mCM.registerForDisconnect(this, PHONE_DISCONNECT, null);
mCM.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
mCM.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
mCM.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null);
mCM.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
mCM.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
mCM.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
mCM.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null);
mCM.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null);
mCM.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null);
mCM.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null);
}
消息处理部分
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case PHONE_NEW_RINGING_CONNECTION:
log("RINGING... (new)");
onNewRingingConnection((AsyncResult) msg.obj);
mSilentRingerRequested = false;
break;
case PHONE_INCOMING_RING:
// repeat the ring when requested by the RIL, and when the user has NOT
// specifically requested silence.
if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
PhoneBase pb = (PhoneBase)((AsyncResult)msg.obj).result;
if ((pb.getState() == Phone.State.RINGING)
&& (mSilentRingerRequested == false)) {
if (DBG) log("RINGING... (PHONE_INCOMING_RING event)");
mRinger.ring();
} else {
if (DBG) log("RING before NEW_RING, skipping");
}
}
break;
case PHONE_STATE_CHANGED:
onPhoneStateChanged((AsyncResult) msg.obj);
break;
case PHONE_DISCONNECT:
if (DBG) log("DISCONNECT");
onDisconnect((AsyncResult) msg.obj);
break;
case PHONE_UNKNOWN_CONNECTION_APPEARED:
onUnknownConnectionAppeared((AsyncResult) msg.obj);
break;
case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT:
// CallerInfo query is taking too long! But we can't wait
// any more, so start ringing NOW even if it means we won't
// use the correct custom ringtone.
Log.w(LOG_TAG, "CallerInfo query took too long; manually starting ringer");
// In this case we call onCustomRingQueryComplete(), just
// like if the query had completed normally. (But we're
// going to get the default ringtone, since we never got
// the chance to call Ringer.setCustomRingtoneUri()).
onCustomRingQueryComplete();
break;
case PHONE_MWI_CHANGED:
onMwiChanged(mApplication.phone.getMessageWaitingIndicator());
break;
case PHONE_BATTERY_LOW:
onBatteryLow();
break;
case PHONE_CDMA_CALL_WAITING:
if (DBG) log("Received PHONE_CDMA_CALL_WAITING event");
onCdmaCallWaiting((AsyncResult) msg.obj);
break;
case CDMA_CALL_WAITING_REJECT:
Log.i(LOG_TAG, "Received CDMA_CALL_WAITING_REJECT event");
onCdmaCallWaitingReject();
break;
case CALLWAITING_CALLERINFO_DISPLAY_DONE:
Log.i(LOG_TAG, "Received CALLWAITING_CALLERINFO_DISPLAY_DONE event");
mCallWaitingTimeOut = true;
onCdmaCallWaitingReject();
break;
case CALLWAITING_ADDCALL_DISABLE_TIMEOUT:
if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ...");
// Set the mAddCallMenuStateAfterCW state to true
mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
mApplication.updateInCallScreen();
break;
case PHONE_STATE_DISPLAYINFO:
if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
onDisplayInfo((AsyncResult) msg.obj);
break;
case PHONE_STATE_SIGNALINFO:
if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
onSignalInfo((AsyncResult) msg.obj);
break;
case DISPLAYINFO_NOTIFICATION_DONE:
if (DBG) log("Received Display Info notification done event ...");
CdmaDisplayInfo.dismissDisplayInfoRecord();
break;
case EVENT_OTA_PROVISION_CHANGE:
if (DBG) log("EVENT_OTA_PROVISION_CHANGE...");
mApplication.handleOtaspEvent(msg);
break;
case PHONE_ENHANCED_VP_ON:
if (DBG) log("PHONE_ENHANCED_VP_ON...");
if (!mVoicePrivacyState) {
int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
new InCallTonePlayer(toneToPlay).start();
mVoicePrivacyState = true;
// Update the VP icon:
if (DBG) log("- updating notification for VP state...");
mApplication.notificationMgr.updateInCallNotification();
}
break;
case PHONE_ENHANCED_VP_OFF:
if (DBG) log("PHONE_ENHANCED_VP_OFF...");
if (mVoicePrivacyState) {
int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
new InCallTonePlayer(toneToPlay).start();
mVoicePrivacyState = false;
// Update the VP icon:
if (DBG) log("- updating notification for VP state...");
mApplication.notificationMgr.updateInCallNotification();
}
break;
case PHONE_RINGBACK_TONE:
onRingbackTone((AsyncResult) msg.obj);
break;
case PHONE_RESEND_MUTE:
onResendMute();
break;
case UPDATE_IN_CALL_NOTIFICATION:
mApplication.notificationMgr.updateInCallNotification();
break;
default:
// super.handleMessage(msg);
}
}
主要分析通话状态的改变标记:PHONE_STATE_CHANGED
case PHONE_STATE_CHANGED:
onPhoneStateChanged((AsyncResult) msg.obj);
break;
根据不同的状态处理
/**
* Updates the phone UI in response to phone state changes.
*
* Watch out: certain state changes are actually handled by their own
* specific methods:
* - see onNewRingingConnection() for new incoming calls
* - see onDisconnect() for calls being hung up or disconnected
*/
private void onPhoneStateChanged(AsyncResult r) {
Phone.State state = mCM.getState();
if (VDBG) log("onPhoneStateChanged: state = " + state);
// Turn status bar notifications on or off depending upon the state
// of the phone. Notification Alerts (audible or vibrating) should
// be on if and only if the phone is IDLE.
mApplication.notificationMgr.statusBarHelper
.enableNotificationAlerts(state == Phone.State.IDLE);
Phone fgPhone = mCM.getFgPhone();
if (fgPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE)
&& ((mPreviousCdmaCallState == Call.State.DIALING)
|| (mPreviousCdmaCallState == Call.State.ALERTING))) {
if (mIsCdmaRedialCall) {
int toneToPlay = InCallTonePlayer.TONE_REDIAL;
new InCallTonePlayer(toneToPlay).start();
}
// Stop any signal info tone when call moves to ACTIVE state
stopSignalInfoTone();
}
mPreviousCdmaCallState = fgPhone.getForegroundCall().getState();
}
// Have the PhoneApp recompute its mShowBluetoothIndication
// flag based on the (new) telephony state.
// There's no need to force a UI update since we update the
// in-call notification ourselves (below), and the InCallScreen
// listens for phone state changes itself.
mApplication.updateBluetoothIndication(false);
// Update the proximity sensor mode (on devices that have a
// proximity sensor).
mApplication.updatePhoneState(state);
if (state == Phone.State.OFFHOOK) {
// stop call waiting tone if needed when answering
if (mCallWaitingTonePlayer != null) {
mCallWaitingTonePlayer.stopTone();
mCallWaitingTonePlayer = null;
}
if (VDBG) log("onPhoneStateChanged: OFF HOOK");
// make sure audio is in in-call mode now
PhoneUtils.setAudioMode(mCM);
// if the call screen is showing, let it handle the event,
// otherwise handle it here.
if (!mApplication.isShowingCallScreen()) {
mApplication.setScreenTimeout(PhoneApp.ScreenTimeoutDuration.DEFAULT);
mApplication.requestWakeState(PhoneApp.WakeState.SLEEP);
}
// Since we're now in-call, the Ringer should definitely *not*
// be ringing any more. (This is just a sanity-check; we
// already stopped the ringer explicitly back in
// PhoneUtils.answerCall(), before the call to phone.acceptCall().)
// TODO: Confirm that this call really *is* unnecessary, and if so,
// remove it!
if (DBG) log("stopRing()... (OFFHOOK state)");
mRinger.stopRing();
// Post a request to update the "in-call" status bar icon.
//
// We don't call NotificationMgr.updateInCallNotification()
// directly here, for two reasons:
// (1) a single phone state change might actually trigger multiple
// onPhoneStateChanged() callbacks, so this prevents redundant
// updates of the notification.
// (2) we suppress the status bar icon while the in-call UI is
// visible (see updateInCallNotification()). But when launching
// an outgoing call the phone actually goes OFFHOOK slightly
// *before* the InCallScreen comes up, so the delay here avoids a
// brief flicker of the icon at that point.
if (DBG) log("- posting UPDATE_IN_CALL_NOTIFICATION request...");
// Remove any previous requests in the queue
removeMessages(UPDATE_IN_CALL_NOTIFICATION);
final int IN_CALL_NOTIFICATION_UPDATE_DELAY = 1000; // msec
sendEmptyMessageDelayed(UPDATE_IN_CALL_NOTIFICATION,
IN_CALL_NOTIFICATION_UPDATE_DELAY);
}
if (fgPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
Connection c = fgPhone.getForegroundCall().getLatestConnection();
if ((c != null) && (PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
mApplication))) {
if (VDBG) log("onPhoneStateChanged: it is an emergency call.");
Call.State callState = fgPhone.getForegroundCall().getState();
if (mEmergencyTonePlayerVibrator == null) {
mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator();
}
if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
mIsEmergencyToneOn = Settings.System.getInt(
mApplication.getContentResolver(),
Settings.System.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF &&
mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) {
if (mEmergencyTonePlayerVibrator != null) {
mEmergencyTonePlayerVibrator.start();
}
}
} else if (callState == Call.State.ACTIVE) {
if (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF) {
if (mEmergencyTonePlayerVibrator != null) {
mEmergencyTonePlayerVibrator.stop();
}
}
}
}
}
if ((fgPhone.getPhoneType() == Phone.PHONE_TYPE_GSM)
|| (fgPhone.getPhoneType() == Phone.PHONE_TYPE_SIP)) {
Call.State callState = mCM.getActiveFgCallState();
if (!callState.isDialing()) {
// If call get activated or disconnected before the ringback
// tone stops, we have to stop it to prevent disturbing.
if (mInCallRingbackTonePlayer != null) {
mInCallRingbackTonePlayer.stopTone();
mInCallRingbackTonePlayer = null;
}
}
}
}
如果需要对不能的通话状态处理不同的事情可以获取以下状态
Phone.State.OFFHOOK 挂断
Phone.State.RINGING 正在来电铃声状态
Phone.State.IDLE 空间状态
例如在通过结束后关闭InCallScreen界面可以添加以下代码
if(Phone.State.OFFHOOK == state){
finish();
}
后面再分析InCallScreen的UI显示和Framework层