blob: d5db68978020cb08e8baa7cc2191c256c98579e9 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
19import android.app.Activity;
20import android.app.ActivityOptions;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.app.ProgressDialog;
24import android.bluetooth.BluetoothAdapter;
25import android.bluetooth.BluetoothDevice;
26import android.bluetooth.BluetoothHeadset;
27import android.bluetooth.BluetoothProfile;
28import android.bluetooth.IBluetoothHeadsetPhone;
29import android.content.ActivityNotFoundException;
30import android.content.BroadcastReceiver;
31import android.content.Context;
32import android.content.DialogInterface;
33import android.content.DialogInterface.OnCancelListener;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.res.Configuration;
37import android.content.res.Resources;
38import android.graphics.Typeface;
39import android.media.AudioManager;
40import android.os.AsyncResult;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.os.PowerManager;
45import android.os.RemoteException;
46import android.os.SystemClock;
47import android.os.SystemProperties;
48import android.telephony.ServiceState;
49import android.text.TextUtils;
50import android.text.method.DialerKeyListener;
51import android.util.EventLog;
52import android.util.Log;
53import android.view.KeyEvent;
54import android.view.View;
55import android.view.ViewGroup;
56import android.view.ViewStub;
57import android.view.Window;
58import android.view.WindowManager;
59import android.view.accessibility.AccessibilityEvent;
60import android.widget.EditText;
61import android.widget.ImageView;
62import android.widget.LinearLayout;
63import android.widget.TextView;
64import android.widget.Toast;
65
66import com.android.internal.telephony.Call;
67import com.android.internal.telephony.CallManager;
68import com.android.internal.telephony.Connection;
69import com.android.internal.telephony.MmiCode;
70import com.android.internal.telephony.Phone;
71import com.android.internal.telephony.PhoneConstants;
72import com.android.internal.telephony.TelephonyCapabilities;
73import com.android.phone.Constants.CallStatusCode;
74import com.android.phone.InCallUiState.InCallScreenMode;
75import com.android.phone.OtaUtils.CdmaOtaScreenState;
76
77import java.util.List;
78
79
80/**
81 * Phone app "in call" screen.
82 */
83public class InCallScreen extends Activity
84 implements View.OnClickListener {
85 private static final String LOG_TAG = "InCallScreen";
86
87 private static final boolean DBG =
88 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
89 private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
90
91 /**
92 * Intent extra used to specify whether the DTMF dialpad should be
93 * initially visible when bringing up the InCallScreen. (If this
94 * extra is present, the dialpad will be initially shown if the extra
95 * has the boolean value true, and initially hidden otherwise.)
96 */
97 // TODO: Should be EXTRA_SHOW_DIALPAD for consistency.
98 static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
99
100 /**
101 * Intent extra to specify the package name of the gateway
102 * provider. Used to get the name displayed in the in-call screen
103 * during the call setup. The value is a string.
104 */
105 // TODO: This extra is currently set by the gateway application as
106 // a temporary measure. Ultimately, the framework will securely
107 // set it.
108 /* package */ static final String EXTRA_GATEWAY_PROVIDER_PACKAGE =
109 "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
110
111 /**
112 * Intent extra to specify the URI of the provider to place the
113 * call. The value is a string. It holds the gateway address
114 * (phone gateway URL should start with the 'tel:' scheme) that
115 * will actually be contacted to call the number passed in the
116 * intent URL or in the EXTRA_PHONE_NUMBER extra.
117 */
118 // TODO: Should the value be a Uri (Parcelable)? Need to make sure
119 // MMI code '#' don't get confused as URI fragments.
120 /* package */ static final String EXTRA_GATEWAY_URI =
121 "com.android.phone.extra.GATEWAY_URI";
122
123 // Amount of time (in msec) that we display the "Call ended" state.
124 // The "short" value is for calls ended by the local user, and the
125 // "long" value is for calls ended by the remote caller.
126 private static final int CALL_ENDED_SHORT_DELAY = 200; // msec
127 private static final int CALL_ENDED_LONG_DELAY = 2000; // msec
128 private static final int CALL_ENDED_EXTRA_LONG_DELAY = 5000; // msec
129
130 // Amount of time that we display the PAUSE alert Dialog showing the
131 // post dial string yet to be send out to the n/w
132 private static final int PAUSE_PROMPT_DIALOG_TIMEOUT = 2000; //msec
133
134 // Amount of time that we display the provider info if applicable.
135 private static final int PROVIDER_INFO_TIMEOUT = 5000; // msec
136
137 // These are values for the settings of the auto retry mode:
138 // 0 = disabled
139 // 1 = enabled
140 // TODO (Moto):These constants don't really belong here,
141 // they should be moved to Settings where the value is being looked up in the first place
142 static final int AUTO_RETRY_OFF = 0;
143 static final int AUTO_RETRY_ON = 1;
144
145 // Message codes; see mHandler below.
146 // Note message codes < 100 are reserved for the PhoneApp.
147 private static final int PHONE_STATE_CHANGED = 101;
148 private static final int PHONE_DISCONNECT = 102;
149 private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
150 private static final int POST_ON_DIAL_CHARS = 104;
151 private static final int WILD_PROMPT_CHAR_ENTERED = 105;
152 private static final int ADD_VOICEMAIL_NUMBER = 106;
153 private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
154 private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
155 private static final int SUPP_SERVICE_FAILED = 110;
156 private static final int REQUEST_UPDATE_BLUETOOTH_INDICATION = 114;
157 private static final int PHONE_CDMA_CALL_WAITING = 115;
158 private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
159 private static final int REQUEST_CLOSE_OTA_FAILURE_NOTICE = 119;
160 private static final int EVENT_PAUSE_DIALOG_COMPLETE = 120;
161 private static final int EVENT_HIDE_PROVIDER_INFO = 121; // Time to remove the info.
162 private static final int REQUEST_UPDATE_SCREEN = 122;
163 private static final int PHONE_INCOMING_RING = 123;
164 private static final int PHONE_NEW_RINGING_CONNECTION = 124;
165
166 // When InCallScreenMode is UNDEFINED set the default action
167 // to ACTION_UNDEFINED so if we are resumed the activity will
168 // know its undefined. In particular checkIsOtaCall will return
169 // false.
170 public static final String ACTION_UNDEFINED = "com.android.phone.InCallScreen.UNDEFINED";
171
172 /** Status codes returned from syncWithPhoneState(). */
173 private enum SyncWithPhoneStateStatus {
174 /**
175 * Successfully updated our internal state based on the telephony state.
176 */
177 SUCCESS,
178
179 /**
180 * There was no phone state to sync with (i.e. the phone was
181 * completely idle). In most cases this means that the
182 * in-call UI shouldn't be visible in the first place, unless
183 * we need to remain in the foreground while displaying an
184 * error message.
185 */
186 PHONE_NOT_IN_USE
187 }
188
189 private boolean mRegisteredForPhoneStates;
190
191 private PhoneGlobals mApp;
192 private CallManager mCM;
193
194 // TODO: need to clean up all remaining uses of mPhone.
195 // (There may be more than one Phone instance on the device, so it's wrong
196 // to just keep a single mPhone field. Instead, any time we need a Phone
197 // reference we should get it dynamically from the CallManager, probably
198 // based on the current foreground Call.)
199 private Phone mPhone;
200
201 private BluetoothHeadset mBluetoothHeadset;
202 private BluetoothAdapter mBluetoothAdapter;
203 private boolean mBluetoothConnectionPending;
204 private long mBluetoothConnectionRequestTime;
205
206 /** Main in-call UI elements. */
207 private CallCard mCallCard;
208
209 // UI controls:
210 private InCallControlState mInCallControlState;
211 private InCallTouchUi mInCallTouchUi;
212 private RespondViaSmsManager mRespondViaSmsManager; // see internalRespondViaSms()
213 private ManageConferenceUtils mManageConferenceUtils;
214
215 // DTMF Dialer controller and its view:
216 private DTMFTwelveKeyDialer mDialer;
217
218 private EditText mWildPromptText;
219
220 // Various dialogs we bring up (see dismissAllDialogs()).
221 // TODO: convert these all to use the "managed dialogs" framework.
222 //
223 // The MMI started dialog can actually be one of 2 items:
224 // 1. An alert dialog if the MMI code is a normal MMI
225 // 2. A progress dialog if the user requested a USSD
226 private Dialog mMmiStartedDialog;
227 private AlertDialog mMissingVoicemailDialog;
228 private AlertDialog mGenericErrorDialog;
229 private AlertDialog mSuppServiceFailureDialog;
230 private AlertDialog mWaitPromptDialog;
231 private AlertDialog mWildPromptDialog;
232 private AlertDialog mCallLostDialog;
233 private AlertDialog mPausePromptDialog;
234 private AlertDialog mExitingECMDialog;
235 // NOTE: if you add a new dialog here, be sure to add it to dismissAllDialogs() also.
236
237 // ProgressDialog created by showProgressIndication()
238 private ProgressDialog mProgressDialog;
239
240 // TODO: If the Activity class ever provides an easy way to get the
241 // current "activity lifecycle" state, we can remove these flags.
242 private boolean mIsDestroyed = false;
243 private boolean mIsForegroundActivity = false;
244 private boolean mIsForegroundActivityForProximity = false;
245 private PowerManager mPowerManager;
246
247 // For use with Pause/Wait dialogs
248 private String mPostDialStrAfterPause;
249 private boolean mPauseInProgress = false;
250
251 // Info about the most-recently-disconnected Connection, which is used
252 // to determine what should happen when exiting the InCallScreen after a
253 // call. (This info is set by onDisconnect(), and used by
254 // delayedCleanupAfterDisconnect().)
255 private Connection.DisconnectCause mLastDisconnectCause;
256
257 /** In-call audio routing options; see switchInCallAudio(). */
258 public enum InCallAudioMode {
259 SPEAKER, // Speakerphone
260 BLUETOOTH, // Bluetooth headset (if available)
261 EARPIECE, // Handset earpiece (or wired headset, if connected)
262 }
263
264
265 private Handler mHandler = new Handler() {
266 @Override
267 public void handleMessage(Message msg) {
268 if (mIsDestroyed) {
269 if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
270 return;
271 }
272 if (!mIsForegroundActivity) {
273 if (DBG) log("Handler: handling message " + msg + " while not in foreground");
274 // Continue anyway; some of the messages below *want* to
275 // be handled even if we're not the foreground activity
276 // (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
277 // should at least be safe to handle if we're not in the
278 // foreground...
279 }
280
281 switch (msg.what) {
282 case SUPP_SERVICE_FAILED:
283 onSuppServiceFailed((AsyncResult) msg.obj);
284 break;
285
286 case PHONE_STATE_CHANGED:
287 onPhoneStateChanged((AsyncResult) msg.obj);
288 break;
289
290 case PHONE_DISCONNECT:
291 onDisconnect((AsyncResult) msg.obj);
292 break;
293
294 case EVENT_HEADSET_PLUG_STATE_CHANGED:
295 // Update the in-call UI, since some UI elements (such
296 // as the "Speaker" button) may change state depending on
297 // whether a headset is plugged in.
298 // TODO: A full updateScreen() is overkill here, since
299 // the value of PhoneApp.isHeadsetPlugged() only affects a
300 // single onscreen UI element. (But even a full updateScreen()
301 // is still pretty cheap, so let's keep this simple
302 // for now.)
303 updateScreen();
304
305 // Also, force the "audio mode" popup to refresh itself if
306 // it's visible, since one of its items is either "Wired
307 // headset" or "Handset earpiece" depending on whether the
308 // headset is plugged in or not.
309 mInCallTouchUi.refreshAudioModePopup(); // safe even if the popup's not active
310
311 break;
312
313 // TODO: sort out MMI code (probably we should remove this method entirely).
314 // See also MMI handling code in onResume()
315 // case PhoneApp.MMI_INITIATE:
316 // onMMIInitiate((AsyncResult) msg.obj);
317 // break;
318
319 case PhoneGlobals.MMI_CANCEL:
320 onMMICancel();
321 break;
322
323 // handle the mmi complete message.
324 // since the message display class has been replaced with
325 // a system dialog in PhoneUtils.displayMMIComplete(), we
326 // should finish the activity here to close the window.
327 case PhoneGlobals.MMI_COMPLETE:
328 onMMIComplete((MmiCode) ((AsyncResult) msg.obj).result);
329 break;
330
331 case POST_ON_DIAL_CHARS:
332 handlePostOnDialChars((AsyncResult) msg.obj, (char) msg.arg1);
333 break;
334
335 case ADD_VOICEMAIL_NUMBER:
336 addVoiceMailNumberPanel();
337 break;
338
339 case DONT_ADD_VOICEMAIL_NUMBER:
340 dontAddVoiceMailNumber();
341 break;
342
343 case DELAYED_CLEANUP_AFTER_DISCONNECT:
344 delayedCleanupAfterDisconnect();
345 break;
346
347 case REQUEST_UPDATE_BLUETOOTH_INDICATION:
348 if (VDBG) log("REQUEST_UPDATE_BLUETOOTH_INDICATION...");
349 // The bluetooth headset state changed, so some UI
350 // elements may need to update. (There's no need to
351 // look up the current state here, since any UI
352 // elements that care about the bluetooth state get it
353 // directly from PhoneApp.showBluetoothIndication().)
354 updateScreen();
355 break;
356
357 case PHONE_CDMA_CALL_WAITING:
358 if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
359 Connection cn = mCM.getFirstActiveRingingCall().getLatestConnection();
360
361 // Only proceed if we get a valid connection object
362 if (cn != null) {
363 // Finally update screen with Call waiting info and request
364 // screen to wake up
365 updateScreen();
366 mApp.updateWakeState();
367 }
368 break;
369
370 case REQUEST_CLOSE_SPC_ERROR_NOTICE:
371 if (mApp.otaUtils != null) {
372 mApp.otaUtils.onOtaCloseSpcNotice();
373 }
374 break;
375
376 case REQUEST_CLOSE_OTA_FAILURE_NOTICE:
377 if (mApp.otaUtils != null) {
378 mApp.otaUtils.onOtaCloseFailureNotice();
379 }
380 break;
381
382 case EVENT_PAUSE_DIALOG_COMPLETE:
383 if (mPausePromptDialog != null) {
384 if (DBG) log("- DISMISSING mPausePromptDialog.");
385 mPausePromptDialog.dismiss(); // safe even if already dismissed
386 mPausePromptDialog = null;
387 }
388 break;
389
390 case EVENT_HIDE_PROVIDER_INFO:
391 mApp.inCallUiState.providerInfoVisible = false;
392 if (mCallCard != null) {
393 mCallCard.updateState(mCM);
394 }
395 break;
396 case REQUEST_UPDATE_SCREEN:
397 updateScreen();
398 break;
399
400 case PHONE_INCOMING_RING:
401 onIncomingRing();
402 break;
403
404 case PHONE_NEW_RINGING_CONNECTION:
405 onNewRingingConnection();
406 break;
407
408 default:
409 Log.wtf(LOG_TAG, "mHandler: unexpected message: " + msg);
410 break;
411 }
412 }
413 };
414
415 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
416 @Override
417 public void onReceive(Context context, Intent intent) {
418 String action = intent.getAction();
419 if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
420 // Listen for ACTION_HEADSET_PLUG broadcasts so that we
421 // can update the onscreen UI when the headset state changes.
422 // if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
423 // if (DBG) log("==> intent: " + intent);
424 // if (DBG) log(" state: " + intent.getIntExtra("state", 0));
425 // if (DBG) log(" name: " + intent.getStringExtra("name"));
426 // send the event and add the state as an argument.
427 Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
428 intent.getIntExtra("state", 0), 0);
429 mHandler.sendMessage(message);
430 }
431 }
432 };
433
434
435 @Override
436 protected void onCreate(Bundle icicle) {
437 Log.i(LOG_TAG, "onCreate()... this = " + this);
438 Profiler.callScreenOnCreate();
439 super.onCreate(icicle);
440
441 // Make sure this is a voice-capable device.
442 if (!PhoneGlobals.sVoiceCapable) {
443 // There should be no way to ever reach the InCallScreen on a
444 // non-voice-capable device, since this activity is not exported by
445 // our manifest, and we explicitly disable any other external APIs
446 // like the CALL intent and ITelephony.showCallScreen().
447 // So the fact that we got here indicates a phone app bug.
448 Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");
449 finish();
450 return;
451 }
452
453 mApp = PhoneGlobals.getInstance();
454 mApp.setInCallScreenInstance(this);
455
456 // set this flag so this activity will stay in front of the keyguard
457 int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
458 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
459 if (mApp.getPhoneState() == PhoneConstants.State.OFFHOOK) {
460 // While we are in call, the in-call screen should dismiss the keyguard.
461 // This allows the user to press Home to go directly home without going through
462 // an insecure lock screen.
463 // But we do not want to do this if there is no active call so we do not
464 // bypass the keyguard if the call is not answered or declined.
465 flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
466 }
467
468 WindowManager.LayoutParams lp = getWindow().getAttributes();
469 lp.flags |= flags;
470 if (!mApp.proximitySensorModeEnabled()) {
471 // If we don't have a proximity sensor, then the in-call screen explicitly
472 // controls user activity. This is to prevent spurious touches from waking
473 // the display.
474 lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
475 }
476 getWindow().setAttributes(lp);
477
478 setPhone(mApp.phone); // Sets mPhone
479
480 mCM = mApp.mCM;
481 log("- onCreate: phone state = " + mCM.getState());
482
483 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
484 if (mBluetoothAdapter != null) {
485 mBluetoothAdapter.getProfileProxy(getApplicationContext(), mBluetoothProfileServiceListener,
486 BluetoothProfile.HEADSET);
487 }
488
489 requestWindowFeature(Window.FEATURE_NO_TITLE);
490
491 // Inflate everything in incall_screen.xml and add it to the screen.
492 setContentView(R.layout.incall_screen);
493
494 // If in landscape, then one of the ViewStubs (instead of <include>) is used for the
495 // incall_touch_ui, because CDMA and GSM button layouts are noticeably different.
496 final ViewStub touchUiStub = (ViewStub) findViewById(
497 mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
498 ? R.id.inCallTouchUiCdmaStub : R.id.inCallTouchUiStub);
499 if (touchUiStub != null) touchUiStub.inflate();
500
501 initInCallScreen();
502
503 registerForPhoneStates();
504
505 // No need to change wake state here; that happens in onResume() when we
506 // are actually displayed.
507
508 // Handle the Intent we were launched with, but only if this is the
509 // the very first time we're being launched (ie. NOT if we're being
510 // re-initialized after previously being shut down.)
511 // Once we're up and running, any future Intents we need
512 // to handle will come in via the onNewIntent() method.
513 if (icicle == null) {
514 if (DBG) log("onCreate(): this is our very first launch, checking intent...");
515 internalResolveIntent(getIntent());
516 }
517
518 Profiler.callScreenCreated();
519 if (DBG) log("onCreate(): exit");
520 }
521
522 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
523 new BluetoothProfile.ServiceListener() {
524 @Override
525 public void onServiceConnected(int profile, BluetoothProfile proxy) {
526 mBluetoothHeadset = (BluetoothHeadset) proxy;
527 if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
528 }
529
530 @Override
531 public void onServiceDisconnected(int profile) {
532 mBluetoothHeadset = null;
533 }
534 };
535
536 /**
537 * Sets the Phone object used internally by the InCallScreen.
538 *
539 * In normal operation this is called from onCreate(), and the
540 * passed-in Phone object comes from the PhoneApp.
541 * For testing, test classes can use this method to
542 * inject a test Phone instance.
543 */
544 /* package */ void setPhone(Phone phone) {
545 mPhone = phone;
546 }
547
548 @Override
549 protected void onResume() {
550 if (DBG) log("onResume()...");
551 super.onResume();
552
553 mIsForegroundActivity = true;
554 mIsForegroundActivityForProximity = true;
555
556 // The flag shouldn't be turned on when there are actual phone calls.
557 if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()) {
558 mApp.inCallUiState.showAlreadyDisconnectedState = false;
559 }
560
561 final InCallUiState inCallUiState = mApp.inCallUiState;
562 if (VDBG) inCallUiState.dumpState();
563
564 updateExpandedViewState();
565
566 // ...and update the in-call notification too, since the status bar
567 // icon needs to be hidden while we're the foreground activity:
568 mApp.notificationMgr.updateInCallNotification();
569
570 // Listen for broadcast intents that might affect the onscreen UI.
571 registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
572
573 // Keep a "dialer session" active when we're in the foreground.
574 // (This is needed to play DTMF tones.)
575 mDialer.startDialerSession();
576
577 // Restore various other state from the InCallUiState object:
578
579 // Update the onscreen dialpad state to match the InCallUiState.
580 if (inCallUiState.showDialpad) {
581 openDialpadInternal(false); // no "opening" animation
582 } else {
583 closeDialpadInternal(false); // no "closing" animation
584 }
585
586 // Reset the dialpad context
587 // TODO: Dialpad digits should be set here as well (once they are saved)
588 mDialer.setDialpadContext(inCallUiState.dialpadContextText);
589
590 // If there's a "Respond via SMS" popup still around since the
591 // last time we were the foreground activity, make sure it's not
592 // still active!
593 // (The popup should *never* be visible initially when we first
594 // come to the foreground; it only ever comes up in response to
595 // the user selecting the "SMS" option from the incoming call
596 // widget.)
597 mRespondViaSmsManager.dismissPopup(); // safe even if already dismissed
598
599 // Display an error / diagnostic indication if necessary.
600 //
601 // When the InCallScreen comes to the foreground, we normally we
602 // display the in-call UI in whatever state is appropriate based on
603 // the state of the telephony framework (e.g. an outgoing call in
604 // DIALING state, an incoming call, etc.)
605 //
606 // But if the InCallUiState has a "pending call status code" set,
607 // that means we need to display some kind of status or error
608 // indication to the user instead of the regular in-call UI. (The
609 // most common example of this is when there's some kind of
610 // failure while initiating an outgoing call; see
611 // CallController.placeCall().)
612 //
613 boolean handledStartupError = false;
614 if (inCallUiState.hasPendingCallStatusCode()) {
615 if (DBG) log("- onResume: need to show status indication!");
616 showStatusIndication(inCallUiState.getPendingCallStatusCode());
617
618 // Set handledStartupError to ensure that we won't bail out below.
619 // (We need to stay here in the InCallScreen so that the user
620 // is able to see the error dialog!)
621 handledStartupError = true;
622 }
623
624 // Set the volume control handler while we are in the foreground.
625 final boolean bluetoothConnected = isBluetoothAudioConnected();
626
627 if (bluetoothConnected) {
628 setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
629 } else {
630 setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
631 }
632
633 takeKeyEvents(true);
634
635 // If an OTASP call is in progress, use the special OTASP-specific UI.
636 boolean inOtaCall = false;
637 if (TelephonyCapabilities.supportsOtasp(mPhone)) {
638 inOtaCall = checkOtaspStateOnResume();
639 }
640 if (!inOtaCall) {
641 // Always start off in NORMAL mode
642 setInCallScreenMode(InCallScreenMode.NORMAL);
643 }
644
645 // Before checking the state of the CallManager, clean up any
646 // connections in the DISCONNECTED state.
647 // (The DISCONNECTED state is used only to drive the "call ended"
648 // UI; it's totally useless when *entering* the InCallScreen.)
649 mCM.clearDisconnected();
650
651 // Update the onscreen UI to reflect the current telephony state.
652 SyncWithPhoneStateStatus status = syncWithPhoneState();
653
654 // Note there's no need to call updateScreen() here;
655 // syncWithPhoneState() already did that if necessary.
656
657 if (status != SyncWithPhoneStateStatus.SUCCESS) {
658 if (DBG) log("- onResume: syncWithPhoneState failed! status = " + status);
659 // Couldn't update the UI, presumably because the phone is totally
660 // idle.
661
662 // Even though the phone is idle, though, we do still need to
663 // stay here on the InCallScreen if we're displaying an
664 // error dialog (see "showStatusIndication()" above).
665
666 if (handledStartupError) {
667 // Stay here for now. We'll eventually leave the
668 // InCallScreen when the user presses the dialog's OK
669 // button (see bailOutAfterErrorDialog()), or when the
670 // progress indicator goes away.
671 Log.i(LOG_TAG, " ==> syncWithPhoneState failed, but staying here anyway.");
672 } else {
673 // The phone is idle, and we did NOT handle a
674 // startup error during this pass thru onResume.
675 //
676 // This basically means that we're being resumed because of
677 // some action *other* than a new intent. (For example,
678 // the user pressing POWER to wake up the device, causing
679 // the InCallScreen to come back to the foreground.)
680 //
681 // In this scenario we do NOT want to stay here on the
682 // InCallScreen: we're not showing any useful info to the
683 // user (like a dialog), and the in-call UI itself is
684 // useless if there's no active call. So bail out.
685
686 Log.i(LOG_TAG, " ==> syncWithPhoneState failed; bailing out!");
687 dismissAllDialogs();
688
689 // Force the InCallScreen to truly finish(), rather than just
690 // moving it to the back of the activity stack (which is what
691 // our finish() method usually does.)
692 // This is necessary to avoid an obscure scenario where the
693 // InCallScreen can get stuck in an inconsistent state, somehow
694 // causing a *subsequent* outgoing call to fail (bug 4172599).
695 endInCallScreenSession(true /* force a real finish() call */);
696 return;
697 }
698 } else if (TelephonyCapabilities.supportsOtasp(mPhone)) {
699 if (inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL ||
700 inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED) {
701 if (mCallCard != null) mCallCard.setVisibility(View.GONE);
702 updateScreen();
703 return;
704 }
705 }
706
707 // InCallScreen is now active.
708 EventLog.writeEvent(EventLogTags.PHONE_UI_ENTER);
709
710 // Update the poke lock and wake lock when we move to the foreground.
711 // This will be no-op when prox sensor is effective.
712 mApp.updateWakeState();
713
714 // Restore the mute state if the last mute state change was NOT
715 // done by the user.
716 if (mApp.getRestoreMuteOnInCallResume()) {
717 // Mute state is based on the foreground call
718 PhoneUtils.restoreMuteState();
719 mApp.setRestoreMuteOnInCallResume(false);
720 }
721
722 Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
723
724 // If there's a pending MMI code, we'll show a dialog here.
725 //
726 // Note: previously we had shown the dialog when MMI_INITIATE event's coming
727 // from telephony layer, while right now we don't because the event comes
728 // too early (before in-call screen is prepared).
729 // Now we instead check pending MMI code and show the dialog here.
730 //
731 // This *may* cause some problem, e.g. when the user really quickly starts
732 // MMI sequence and calls an actual phone number before the MMI request
733 // being completed, which is rather rare.
734 //
735 // TODO: streamline this logic and have a UX in a better manner.
736 // Right now syncWithPhoneState() above will return SUCCESS based on
737 // mPhone.getPendingMmiCodes().isEmpty(), while we check it again here.
738 // Also we show pre-populated in-call UI under the dialog, which looks
739 // not great. (issue 5210375, 5545506)
740 // After cleaning them, remove commented-out MMI handling code elsewhere.
741 if (!mPhone.getPendingMmiCodes().isEmpty()) {
742 if (mMmiStartedDialog == null) {
743 MmiCode mmiCode = mPhone.getPendingMmiCodes().get(0);
744 Message message = Message.obtain(mHandler, PhoneGlobals.MMI_CANCEL);
745 mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
746 message, mMmiStartedDialog);
747 // mInCallScreen needs to receive MMI_COMPLETE/MMI_CANCEL event from telephony,
748 // which will dismiss the entire screen.
749 }
750 }
751
752 // This means the screen is shown even though there's no connection, which only happens
753 // when the phone call has hung up while the screen is turned off at that moment.
754 // We want to show "disconnected" state with photos with appropriate elapsed time for
755 // the finished phone call.
756 if (mApp.inCallUiState.showAlreadyDisconnectedState) {
757 // if (DBG) {
758 log("onResume(): detected \"show already disconnected state\" situation."
759 + " set up DELAYED_CLEANUP_AFTER_DISCONNECT message with "
760 + CALL_ENDED_LONG_DELAY + " msec delay.");
761 //}
762 mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
763 mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
764 CALL_ENDED_LONG_DELAY);
765 }
766
767 if (VDBG) log("onResume() done.");
768 }
769
770 // onPause is guaranteed to be called when the InCallScreen goes
771 // in the background.
772 @Override
773 protected void onPause() {
774 if (DBG) log("onPause()...");
775 super.onPause();
776
777 if (mPowerManager.isScreenOn()) {
778 // Set to false when the screen went background *not* by screen turned off. Probably
779 // the user bailed out of the in-call screen (by pressing BACK, HOME, etc.)
780 mIsForegroundActivityForProximity = false;
781 }
782 mIsForegroundActivity = false;
783
784 // Force a clear of the provider info frame. Since the
785 // frame is removed using a timed message, it is
786 // possible we missed it if the prev call was interrupted.
787 mApp.inCallUiState.providerInfoVisible = false;
788
789 // "show-already-disconnected-state" should be effective just during the first wake-up.
790 // We should never allow it to stay true after that.
791 mApp.inCallUiState.showAlreadyDisconnectedState = false;
792
793 // A safety measure to disable proximity sensor in case call failed
794 // and the telephony state did not change.
795 mApp.setBeginningCall(false);
796
797 // Make sure the "Manage conference" chronometer is stopped when
798 // we move away from the foreground.
799 mManageConferenceUtils.stopConferenceTime();
800
801 // as a catch-all, make sure that any dtmf tones are stopped
802 // when the UI is no longer in the foreground.
803 mDialer.onDialerKeyUp(null);
804
805 // Release any "dialer session" resources, now that we're no
806 // longer in the foreground.
807 mDialer.stopDialerSession();
808
809 // If the device is put to sleep as the phone call is ending,
810 // we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
811 // event gets handled AFTER the device goes to sleep and wakes
812 // up again.
813
814 // This is because it is possible for a sleep command
815 // (executed with the End Call key) to come during the 2
816 // seconds that the "Call Ended" screen is up. Sleep then
817 // pauses the device (including the cleanup event) and
818 // resumes the event when it wakes up.
819
820 // To fix this, we introduce a bit of code that pushes the UI
821 // to the background if we pause and see a request to
822 // DELAYED_CLEANUP_AFTER_DISCONNECT.
823
824 // Note: We can try to finish directly, by:
825 // 1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
826 // 2. Calling delayedCleanupAfterDisconnect directly
827
828 // However, doing so can cause problems between the phone
829 // app and the keyguard - the keyguard is trying to sleep at
830 // the same time that the phone state is changing. This can
831 // end up causing the sleep request to be ignored.
832 if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)
833 && mCM.getState() != PhoneConstants.State.RINGING) {
834 if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
835 endInCallScreenSession();
836 }
837
838 EventLog.writeEvent(EventLogTags.PHONE_UI_EXIT);
839
840 // Dismiss any dialogs we may have brought up, just to be 100%
841 // sure they won't still be around when we get back here.
842 dismissAllDialogs();
843
844 updateExpandedViewState();
845
846 // ...and the in-call notification too:
847 mApp.notificationMgr.updateInCallNotification();
848 // ...and *always* reset the system bar back to its normal state
849 // when leaving the in-call UI.
850 // (While we're the foreground activity, we disable navigation in
851 // some call states; see InCallTouchUi.updateState().)
852 mApp.notificationMgr.statusBarHelper.enableSystemBarNavigation(true);
853
854 // Unregister for broadcast intents. (These affect the visible UI
855 // of the InCallScreen, so we only care about them while we're in the
856 // foreground.)
857 unregisterReceiver(mReceiver);
858
859 // Make sure we revert the poke lock and wake lock when we move to
860 // the background.
861 mApp.updateWakeState();
862
863 // clear the dismiss keyguard flag so we are back to the default state
864 // when we next resume
865 updateKeyguardPolicy(false);
866
867 // See also PhoneApp#updatePhoneState(), which takes care of all the other release() calls.
868 if (mApp.getUpdateLock().isHeld() && mApp.getPhoneState() == PhoneConstants.State.IDLE) {
869 if (DBG) {
870 log("Release UpdateLock on onPause() because there's no active phone call.");
871 }
872 mApp.getUpdateLock().release();
873 }
874 }
875
876 @Override
877 protected void onStop() {
878 if (DBG) log("onStop()...");
879 super.onStop();
880
881 stopTimer();
882
883 PhoneConstants.State state = mCM.getState();
884 if (DBG) log("onStop: state = " + state);
885
886 if (state == PhoneConstants.State.IDLE) {
887 if (mRespondViaSmsManager.isShowingPopup()) {
888 // This means that the user has been opening the "Respond via SMS" dialog even
889 // after the incoming call hanging up, and the screen finally went background.
890 // In that case we just close the dialog and exit the whole in-call screen.
891 mRespondViaSmsManager.dismissPopup();
892 }
893
894 // when OTA Activation, OTA Success/Failure dialog or OTA SPC
895 // failure dialog is running, do not destroy inCallScreen. Because call
896 // is already ended and dialog will not get redrawn on slider event.
897 if ((mApp.cdmaOtaProvisionData != null) && (mApp.cdmaOtaScreenState != null)
898 && ((mApp.cdmaOtaScreenState.otaScreenState !=
899 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION)
900 && (mApp.cdmaOtaScreenState.otaScreenState !=
901 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG)
902 && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
903 // we don't want the call screen to remain in the activity history
904 // if there are not active or ringing calls.
905 if (DBG) log("- onStop: calling finish() to clear activity history...");
906 moveTaskToBack(true);
907 if (mApp.otaUtils != null) {
908 mApp.otaUtils.cleanOtaScreen(true);
909 }
910 }
911 }
912 }
913
914 @Override
915 protected void onDestroy() {
916 Log.i(LOG_TAG, "onDestroy()... this = " + this);
917 super.onDestroy();
918
919 // Set the magic flag that tells us NOT to handle any handler
920 // messages that come in asynchronously after we get destroyed.
921 mIsDestroyed = true;
922
923 mApp.setInCallScreenInstance(null);
924
925 // Clear out the InCallScreen references in various helper objects
926 // (to let them know we've been destroyed).
927 if (mCallCard != null) {
928 mCallCard.setInCallScreenInstance(null);
929 }
930 if (mInCallTouchUi != null) {
931 mInCallTouchUi.setInCallScreenInstance(null);
932 }
933 mRespondViaSmsManager.setInCallScreenInstance(null);
934
935 mDialer.clearInCallScreenReference();
936 mDialer = null;
937
938 unregisterForPhoneStates();
939 // No need to change wake state here; that happens in onPause() when we
940 // are moving out of the foreground.
941
942 if (mBluetoothHeadset != null) {
943 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
944 mBluetoothHeadset = null;
945 }
946
947 // Dismiss all dialogs, to be absolutely sure we won't leak any of
948 // them while changing orientation.
949 dismissAllDialogs();
950
951 // If there's an OtaUtils instance around, clear out its
952 // references to our internal widgets.
953 if (mApp.otaUtils != null) {
954 mApp.otaUtils.clearUiWidgets();
955 }
956 }
957
958 /**
959 * Dismisses the in-call screen.
960 *
961 * We never *really* finish() the InCallScreen, since we don't want to
962 * get destroyed and then have to be re-created from scratch for the
963 * next call. Instead, we just move ourselves to the back of the
964 * activity stack.
965 *
966 * This also means that we'll no longer be reachable via the BACK
967 * button (since moveTaskToBack() puts us behind the Home app, but the
968 * home app doesn't allow the BACK key to move you any farther down in
969 * the history stack.)
970 *
971 * (Since the Phone app itself is never killed, this basically means
972 * that we'll keep a single InCallScreen instance around for the
973 * entire uptime of the device. This noticeably improves the UI
974 * responsiveness for incoming calls.)
975 */
976 @Override
977 public void finish() {
978 if (DBG) log("finish()...");
979 moveTaskToBack(true);
980 }
981
982 /**
983 * End the current in call screen session.
984 *
985 * This must be called when an InCallScreen session has
986 * complete so that the next invocation via an onResume will
987 * not be in an old state.
988 */
989 public void endInCallScreenSession() {
990 if (DBG) log("endInCallScreenSession()... phone state = " + mCM.getState());
991 endInCallScreenSession(false);
992 }
993
994 /**
995 * Internal version of endInCallScreenSession().
996 *
997 * @param forceFinish If true, force the InCallScreen to
998 * truly finish() rather than just calling moveTaskToBack().
999 * @see finish()
1000 */
1001 private void endInCallScreenSession(boolean forceFinish) {
1002 if (DBG) {
1003 log("endInCallScreenSession(" + forceFinish + ")... phone state = " + mCM.getState());
1004 }
1005 if (forceFinish) {
1006 Log.i(LOG_TAG, "endInCallScreenSession(): FORCING a call to super.finish()!");
1007 super.finish(); // Call super.finish() rather than our own finish() method,
1008 // which actually just calls moveTaskToBack().
1009 } else {
1010 moveTaskToBack(true);
1011 }
1012 setInCallScreenMode(InCallScreenMode.UNDEFINED);
1013
1014 // Call update screen so that the in-call screen goes back to a normal state.
1015 // This avoids bugs where a previous state will filcker the next time phone is
1016 // opened.
1017 updateScreen();
1018
1019 if (mCallCard != null) {
1020 mCallCard.clear();
1021 }
1022 }
1023
1024 /**
1025 * True when this Activity is in foreground (between onResume() and onPause()).
1026 */
1027 /* package */ boolean isForegroundActivity() {
1028 return mIsForegroundActivity;
1029 }
1030
1031 /**
1032 * Returns true when the Activity is in foreground (between onResume() and onPause()),
1033 * or, is in background due to user's bailing out of the screen, not by screen turning off.
1034 *
1035 * @see #isForegroundActivity()
1036 */
1037 /* package */ boolean isForegroundActivityForProximity() {
1038 return mIsForegroundActivityForProximity;
1039 }
1040
1041 /* package */ void updateKeyguardPolicy(boolean dismissKeyguard) {
1042 if (dismissKeyguard) {
1043 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
1044 } else {
1045 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
1046 }
1047 }
1048
1049 private void registerForPhoneStates() {
1050 if (!mRegisteredForPhoneStates) {
1051 mCM.registerForPreciseCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
1052 mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
1053 // TODO: sort out MMI code (probably we should remove this method entirely).
1054 // See also MMI handling code in onResume()
1055 // mCM.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
1056
1057 // register for the MMI complete message. Upon completion,
1058 // PhoneUtils will bring up a system dialog instead of the
1059 // message display class in PhoneUtils.displayMMIComplete().
1060 // We'll listen for that message too, so that we can finish
1061 // the activity at the same time.
1062 mCM.registerForMmiComplete(mHandler, PhoneGlobals.MMI_COMPLETE, null);
1063 mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
1064 mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
1065 mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
1066 mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);
1067 mCM.registerForNewRingingConnection(mHandler, PHONE_NEW_RINGING_CONNECTION, null);
1068 mRegisteredForPhoneStates = true;
1069 }
1070 }
1071
1072 private void unregisterForPhoneStates() {
1073 mCM.unregisterForPreciseCallStateChanged(mHandler);
1074 mCM.unregisterForDisconnect(mHandler);
1075 mCM.unregisterForMmiInitiate(mHandler);
1076 mCM.unregisterForMmiComplete(mHandler);
1077 mCM.unregisterForCallWaiting(mHandler);
1078 mCM.unregisterForPostDialCharacter(mHandler);
1079 mCM.unregisterForSuppServiceFailed(mHandler);
1080 mCM.unregisterForIncomingRing(mHandler);
1081 mCM.unregisterForNewRingingConnection(mHandler);
1082 mRegisteredForPhoneStates = false;
1083 }
1084
1085 /* package */ void updateAfterRadioTechnologyChange() {
1086 if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
1087
1088 // Reset the call screen since the calls cannot be transferred
1089 // across radio technologies.
1090 resetInCallScreenMode();
1091
1092 // Unregister for all events from the old obsolete phone
1093 unregisterForPhoneStates();
1094
1095 // (Re)register for all events relevant to the new active phone
1096 registerForPhoneStates();
1097
1098 // And finally, refresh the onscreen UI. (Note that it's safe
1099 // to call requestUpdateScreen() even if the radio change ended up
1100 // causing us to exit the InCallScreen.)
1101 requestUpdateScreen();
1102 }
1103
1104 @Override
1105 protected void onNewIntent(Intent intent) {
1106 log("onNewIntent: intent = " + intent + ", phone state = " + mCM.getState());
1107
1108 // We're being re-launched with a new Intent. Since it's possible for a
1109 // single InCallScreen instance to persist indefinitely (even if we
1110 // finish() ourselves), this sequence can potentially happen any time
1111 // the InCallScreen needs to be displayed.
1112
1113 // Stash away the new intent so that we can get it in the future
1114 // by calling getIntent(). (Otherwise getIntent() will return the
1115 // original Intent from when we first got created!)
1116 setIntent(intent);
1117
1118 // Activities are always paused before receiving a new intent, so
1119 // we can count on our onResume() method being called next.
1120
1121 // Just like in onCreate(), handle the intent.
1122 internalResolveIntent(intent);
1123 }
1124
1125 private void internalResolveIntent(Intent intent) {
1126 if (intent == null || intent.getAction() == null) {
1127 return;
1128 }
1129 String action = intent.getAction();
1130 if (DBG) log("internalResolveIntent: action=" + action);
1131
1132 // In gingerbread and earlier releases, the InCallScreen used to
1133 // directly handle certain intent actions that could initiate phone
1134 // calls, namely ACTION_CALL and ACTION_CALL_EMERGENCY, and also
1135 // OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING.
1136 //
1137 // But it doesn't make sense to tie those actions to the InCallScreen
1138 // (or especially to the *activity lifecycle* of the InCallScreen).
1139 // Instead, the InCallScreen should only be concerned with running the
1140 // onscreen UI while in a call. So we've now offloaded the call-control
1141 // functionality to a new module called CallController, and OTASP calls
1142 // are now launched from the OtaUtils startInteractiveOtasp() or
1143 // startNonInteractiveOtasp() methods.
1144 //
1145 // So now, the InCallScreen is only ever launched using the ACTION_MAIN
1146 // action, and (upon launch) performs no functionality other than
1147 // displaying the UI in a state that matches the current telephony
1148 // state.
1149
1150 if (action.equals(intent.ACTION_MAIN)) {
1151 // This action is the normal way to bring up the in-call UI.
1152 //
1153 // Most of the interesting work of updating the onscreen UI (to
1154 // match the current telephony state) happens in the
1155 // syncWithPhoneState() => updateScreen() sequence that happens in
1156 // onResume().
1157 //
1158 // But we do check here for one extra that can come along with the
1159 // ACTION_MAIN intent:
1160
1161 if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
1162 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
1163 // dialpad should be initially visible. If the extra isn't
1164 // present at all, we just leave the dialpad in its previous state.
1165
1166 boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
1167 if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
1168
1169 // If SHOW_DIALPAD_EXTRA is specified, that overrides whatever
1170 // the previous state of inCallUiState.showDialpad was.
1171 mApp.inCallUiState.showDialpad = showDialpad;
1172
1173 final boolean hasActiveCall = mCM.hasActiveFgCall();
1174 final boolean hasHoldingCall = mCM.hasActiveBgCall();
1175
1176 // There's only one line in use, AND it's on hold, at which we're sure the user
1177 // wants to use the dialpad toward the exact line, so un-hold the holding line.
1178 if (showDialpad && !hasActiveCall && hasHoldingCall) {
1179 PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
1180 }
1181 }
1182 // ...and in onResume() we'll update the onscreen dialpad state to
1183 // match the InCallUiState.
1184
1185 return;
1186 }
1187
1188 if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {
1189 // Bring up the in-call UI in the OTASP-specific "activate" state;
1190 // see OtaUtils.startInteractiveOtasp(). Note that at this point
1191 // the OTASP call has not been started yet; we won't actually make
1192 // the call until the user presses the "Activate" button.
1193
1194 if (!TelephonyCapabilities.supportsOtasp(mPhone)) {
1195 throw new IllegalStateException(
1196 "Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "
1197 + intent);
1198 }
1199
1200 setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
1201 if ((mApp.cdmaOtaProvisionData != null)
1202 && (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
1203 mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
1204 mApp.cdmaOtaScreenState.otaScreenState =
1205 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
1206 }
1207 return;
1208 }
1209
1210 // Various intent actions that should no longer come here directly:
1211 if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
1212 // This intent is now handled by the InCallScreenShowActivation
1213 // activity, which translates it into a call to
1214 // OtaUtils.startInteractiveOtasp().
1215 throw new IllegalStateException(
1216 "Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "
1217 + intent);
1218 } else if (action.equals(Intent.ACTION_CALL)
1219 || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
1220 // ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now
1221 // translates them into CallController.placeCall() calls rather than
1222 // launching the InCallScreen directly.
1223 throw new IllegalStateException("Unexpected CALL action received by InCallScreen: "
1224 + intent);
1225 } else if (action.equals(ACTION_UNDEFINED)) {
1226 // This action is only used for internal bookkeeping; we should
1227 // never actually get launched with it.
1228 Log.wtf(LOG_TAG, "internalResolveIntent: got launched with ACTION_UNDEFINED");
1229 return;
1230 } else {
1231 Log.wtf(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
1232 // But continue the best we can (basically treating this case
1233 // like ACTION_MAIN...)
1234 return;
1235 }
1236 }
1237
1238 private void stopTimer() {
1239 if (mCallCard != null) mCallCard.stopTimer();
1240 }
1241
1242 private void initInCallScreen() {
1243 if (VDBG) log("initInCallScreen()...");
1244
1245 // Have the WindowManager filter out touch events that are "too fat".
1246 getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
1247
1248 // Initialize the CallCard.
1249 mCallCard = (CallCard) findViewById(R.id.callCard);
1250 if (VDBG) log(" - mCallCard = " + mCallCard);
1251 mCallCard.setInCallScreenInstance(this);
1252
1253 // Initialize the onscreen UI elements.
1254 initInCallTouchUi();
1255
1256 // Helper class to keep track of enabledness/state of UI controls
1257 mInCallControlState = new InCallControlState(this, mCM);
1258
1259 // Helper class to run the "Manage conference" UI
1260 mManageConferenceUtils = new ManageConferenceUtils(this, mCM);
1261
1262 // The DTMF Dialpad.
1263 ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
1264 mDialer = new DTMFTwelveKeyDialer(this, stub);
1265 mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
1266 }
1267
1268 /**
1269 * Returns true if the phone is "in use", meaning that at least one line
1270 * is active (ie. off hook or ringing or dialing). Conversely, a return
1271 * value of false means there's currently no phone activity at all.
1272 */
1273 private boolean phoneIsInUse() {
1274 return mCM.getState() != PhoneConstants.State.IDLE;
1275 }
1276
1277 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
1278 if (VDBG) log("handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
1279
1280 // As soon as the user starts typing valid dialable keys on the
1281 // keyboard (presumably to type DTMF tones) we start passing the
1282 // key events to the DTMFDialer's onDialerKeyDown. We do so
1283 // only if the okToDialDTMFTones() conditions pass.
1284 if (okToDialDTMFTones()) {
1285 return mDialer.onDialerKeyDown(event);
1286
1287 // TODO: If the dialpad isn't currently visible, maybe
1288 // consider automatically bringing it up right now?
1289 // (Just to make sure the user sees the digits widget...)
1290 // But this probably isn't too critical since it's awkward to
1291 // use the hard keyboard while in-call in the first place,
1292 // especially now that the in-call UI is portrait-only...
1293 }
1294
1295 return false;
1296 }
1297
1298 @Override
1299 public void onBackPressed() {
1300 if (DBG) log("onBackPressed()...");
1301
1302 // To consume this BACK press, the code here should just do
1303 // something and return. Otherwise, call super.onBackPressed() to
1304 // get the default implementation (which simply finishes the
1305 // current activity.)
1306
1307 if (mCM.hasActiveRingingCall()) {
1308 // The Back key, just like the Home key, is always disabled
1309 // while an incoming call is ringing. (The user *must* either
1310 // answer or reject the call before leaving the incoming-call
1311 // screen.)
1312 if (DBG) log("BACK key while ringing: ignored");
1313
1314 // And consume this event; *don't* call super.onBackPressed().
1315 return;
1316 }
1317
1318 // BACK is also used to exit out of any "special modes" of the
1319 // in-call UI:
1320
1321 if (mDialer.isOpened()) {
1322 closeDialpadInternal(true); // do the "closing" animation
1323 return;
1324 }
1325
1326 if (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
1327 // Hide the Manage Conference panel, return to NORMAL mode.
1328 setInCallScreenMode(InCallScreenMode.NORMAL);
1329 requestUpdateScreen();
1330 return;
1331 }
1332
1333 // Nothing special to do. Fall back to the default behavior.
1334 super.onBackPressed();
1335 }
1336
1337 /**
1338 * Handles the green CALL key while in-call.
1339 * @return true if we consumed the event.
1340 */
1341 private boolean handleCallKey() {
1342 // The green CALL button means either "Answer", "Unhold", or
1343 // "Swap calls", or can be a no-op, depending on the current state
1344 // of the Phone.
1345
1346 final boolean hasRingingCall = mCM.hasActiveRingingCall();
1347 final boolean hasActiveCall = mCM.hasActiveFgCall();
1348 final boolean hasHoldingCall = mCM.hasActiveBgCall();
1349
1350 int phoneType = mPhone.getPhoneType();
1351 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
1352 // The green CALL button means either "Answer", "Swap calls/On Hold", or
1353 // "Add to 3WC", depending on the current state of the Phone.
1354
1355 CdmaPhoneCallState.PhoneCallState currCallState =
1356 mApp.cdmaPhoneCallState.getCurrentCallState();
1357 if (hasRingingCall) {
1358 //Scenario 1: Accepting the First Incoming and Call Waiting call
1359 if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
1360 internalAnswerCall(); // Automatically holds the current active call,
1361 // if there is one
1362 } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
1363 && (hasActiveCall)) {
1364 //Scenario 2: Merging 3Way calls
1365 if (DBG) log("answerCall: Merge 3-way call scenario");
1366 // Merge calls
1367 PhoneUtils.mergeCalls(mCM);
1368 } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
1369 //Scenario 3: Switching between two Call waiting calls or drop the latest
1370 // connection if in a 3Way merge scenario
1371 if (DBG) log("answerCall: Switch btwn 2 calls scenario");
1372 internalSwapCalls();
1373 }
1374 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
1375 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
1376 if (hasRingingCall) {
1377 // If an incoming call is ringing, the CALL button is actually
1378 // handled by the PhoneWindowManager. (We do this to make
1379 // sure that we'll respond to the key even if the InCallScreen
1380 // hasn't come to the foreground yet.)
1381 //
1382 // We'd only ever get here in the extremely rare case that the
1383 // incoming call started ringing *after*
1384 // PhoneWindowManager.interceptKeyTq() but before the event
1385 // got here, or else if the PhoneWindowManager had some
1386 // problem connecting to the ITelephony service.
1387 Log.w(LOG_TAG, "handleCallKey: incoming call is ringing!"
1388 + " (PhoneWindowManager should have handled this key.)");
1389 // But go ahead and handle the key as normal, since the
1390 // PhoneWindowManager presumably did NOT handle it:
1391
1392 // There's an incoming ringing call: CALL means "Answer".
1393 internalAnswerCall();
1394 } else if (hasActiveCall && hasHoldingCall) {
1395 // Two lines are in use: CALL means "Swap calls".
1396 if (DBG) log("handleCallKey: both lines in use ==> swap calls.");
1397 internalSwapCalls();
1398 } else if (hasHoldingCall) {
1399 // There's only one line in use, AND it's on hold.
1400 // In this case CALL is a shortcut for "unhold".
1401 if (DBG) log("handleCallKey: call on hold ==> unhold.");
1402 PhoneUtils.switchHoldingAndActive(
1403 mCM.getFirstActiveBgCall()); // Really means "unhold" in this state
1404 } else {
1405 // The most common case: there's only one line in use, and
1406 // it's an active call (i.e. it's not on hold.)
1407 // In this case CALL is a no-op.
1408 // (This used to be a shortcut for "add call", but that was a
1409 // bad idea because "Add call" is so infrequently-used, and
1410 // because the user experience is pretty confusing if you
1411 // inadvertently trigger it.)
1412 if (VDBG) log("handleCallKey: call in foregound ==> ignoring.");
1413 // But note we still consume this key event; see below.
1414 }
1415 } else {
1416 throw new IllegalStateException("Unexpected phone type: " + phoneType);
1417 }
1418
1419 // We *always* consume the CALL key, since the system-wide default
1420 // action ("go to the in-call screen") is useless here.
1421 return true;
1422 }
1423
1424 boolean isKeyEventAcceptableDTMF (KeyEvent event) {
1425 return (mDialer != null && mDialer.isKeyEventAcceptable(event));
1426 }
1427
1428 /**
1429 * Overriden to track relevant focus changes.
1430 *
1431 * If a key is down and some time later the focus changes, we may
1432 * NOT recieve the keyup event; logically the keyup event has not
1433 * occured in this window. This issue is fixed by treating a focus
1434 * changed event as an interruption to the keydown, making sure
1435 * that any code that needs to be run in onKeyUp is ALSO run here.
1436 */
1437 @Override
1438 public void onWindowFocusChanged(boolean hasFocus) {
1439 // the dtmf tones should no longer be played
1440 if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
1441 if (!hasFocus && mDialer != null) {
1442 if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
1443 mDialer.onDialerKeyUp(null);
1444 }
1445 }
1446
1447 @Override
1448 public boolean onKeyUp(int keyCode, KeyEvent event) {
1449 // if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
1450
1451 // push input to the dialer.
1452 if ((mDialer != null) && (mDialer.onDialerKeyUp(event))){
1453 return true;
1454 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
1455 // Always consume CALL to be sure the PhoneWindow won't do anything with it
1456 return true;
1457 }
1458 return super.onKeyUp(keyCode, event);
1459 }
1460
1461 @Override
1462 public boolean onKeyDown(int keyCode, KeyEvent event) {
1463 // if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
1464
1465 switch (keyCode) {
1466 case KeyEvent.KEYCODE_CALL:
1467 boolean handled = handleCallKey();
1468 if (!handled) {
1469 Log.w(LOG_TAG, "InCallScreen should always handle KEYCODE_CALL in onKeyDown");
1470 }
1471 // Always consume CALL to be sure the PhoneWindow won't do anything with it
1472 return true;
1473
1474 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
1475 // The standard system-wide handling of the ENDCALL key
1476 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
1477 // already implements exactly what the UI spec wants,
1478 // namely (1) "hang up" if there's a current active call,
1479 // or (2) "don't answer" if there's a current ringing call.
1480
1481 case KeyEvent.KEYCODE_CAMERA:
1482 // Disable the CAMERA button while in-call since it's too
1483 // easy to press accidentally.
1484 return true;
1485
1486 case KeyEvent.KEYCODE_VOLUME_UP:
1487 case KeyEvent.KEYCODE_VOLUME_DOWN:
1488 case KeyEvent.KEYCODE_VOLUME_MUTE:
1489 if (mCM.getState() == PhoneConstants.State.RINGING) {
1490 // If an incoming call is ringing, the VOLUME buttons are
1491 // actually handled by the PhoneWindowManager. (We do
1492 // this to make sure that we'll respond to them even if
1493 // the InCallScreen hasn't come to the foreground yet.)
1494 //
1495 // We'd only ever get here in the extremely rare case that the
1496 // incoming call started ringing *after*
1497 // PhoneWindowManager.interceptKeyTq() but before the event
1498 // got here, or else if the PhoneWindowManager had some
1499 // problem connecting to the ITelephony service.
1500 Log.w(LOG_TAG, "VOLUME key: incoming call is ringing!"
1501 + " (PhoneWindowManager should have handled this key.)");
1502 // But go ahead and handle the key as normal, since the
1503 // PhoneWindowManager presumably did NOT handle it:
1504 internalSilenceRinger();
1505
1506 // As long as an incoming call is ringing, we always
1507 // consume the VOLUME keys.
1508 return true;
1509 }
1510 break;
1511
1512 case KeyEvent.KEYCODE_MUTE:
1513 onMuteClick();
1514 return true;
1515
1516 // Various testing/debugging features, enabled ONLY when VDBG == true.
1517 case KeyEvent.KEYCODE_SLASH:
1518 if (VDBG) {
1519 log("----------- InCallScreen View dump --------------");
1520 // Dump starting from the top-level view of the entire activity:
1521 Window w = this.getWindow();
1522 View decorView = w.getDecorView();
1523 decorView.debug();
1524 return true;
1525 }
1526 break;
1527 case KeyEvent.KEYCODE_EQUALS:
1528 if (VDBG) {
1529 log("----------- InCallScreen call state dump --------------");
1530 PhoneUtils.dumpCallState(mPhone);
1531 PhoneUtils.dumpCallManager();
1532 return true;
1533 }
1534 break;
1535 case KeyEvent.KEYCODE_GRAVE:
1536 if (VDBG) {
1537 // Placeholder for other misc temp testing
1538 log("------------ Temp testing -----------------");
1539 return true;
1540 }
1541 break;
1542 }
1543
1544 if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
1545 return true;
1546 }
1547
1548 return super.onKeyDown(keyCode, event);
1549 }
1550
1551 /**
1552 * Handle a failure notification for a supplementary service
1553 * (i.e. conference, switch, separate, transfer, etc.).
1554 */
1555 void onSuppServiceFailed(AsyncResult r) {
1556 Phone.SuppService service = (Phone.SuppService) r.result;
1557 if (DBG) log("onSuppServiceFailed: " + service);
1558
1559 int errorMessageResId;
1560 switch (service) {
1561 case SWITCH:
1562 // Attempt to switch foreground and background/incoming calls failed
1563 // ("Failed to switch calls")
1564 errorMessageResId = R.string.incall_error_supp_service_switch;
1565 break;
1566
1567 case SEPARATE:
1568 // Attempt to separate a call from a conference call
1569 // failed ("Failed to separate out call")
1570 errorMessageResId = R.string.incall_error_supp_service_separate;
1571 break;
1572
1573 case TRANSFER:
1574 // Attempt to connect foreground and background calls to
1575 // each other (and hanging up user's line) failed ("Call
1576 // transfer failed")
1577 errorMessageResId = R.string.incall_error_supp_service_transfer;
1578 break;
1579
1580 case CONFERENCE:
1581 // Attempt to add a call to conference call failed
1582 // ("Conference call failed")
1583 errorMessageResId = R.string.incall_error_supp_service_conference;
1584 break;
1585
1586 case REJECT:
1587 // Attempt to reject an incoming call failed
1588 // ("Call rejection failed")
1589 errorMessageResId = R.string.incall_error_supp_service_reject;
1590 break;
1591
1592 case HANGUP:
1593 // Attempt to release a call failed ("Failed to release call(s)")
1594 errorMessageResId = R.string.incall_error_supp_service_hangup;
1595 break;
1596
1597 case UNKNOWN:
1598 default:
1599 // Attempt to use a service we don't recognize or support
1600 // ("Unsupported service" or "Selected service failed")
1601 errorMessageResId = R.string.incall_error_supp_service_unknown;
1602 break;
1603 }
1604
1605 // mSuppServiceFailureDialog is a generic dialog used for any
1606 // supp service failure, and there's only ever have one
1607 // instance at a time. So just in case a previous dialog is
1608 // still around, dismiss it.
1609 if (mSuppServiceFailureDialog != null) {
1610 if (DBG) log("- DISMISSING mSuppServiceFailureDialog.");
1611 mSuppServiceFailureDialog.dismiss(); // It's safe to dismiss() a dialog
1612 // that's already dismissed.
1613 mSuppServiceFailureDialog = null;
1614 }
1615
1616 mSuppServiceFailureDialog = new AlertDialog.Builder(this)
1617 .setMessage(errorMessageResId)
1618 .setPositiveButton(R.string.ok, null)
1619 .create();
1620 mSuppServiceFailureDialog.getWindow().addFlags(
1621 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
1622 mSuppServiceFailureDialog.show();
1623 }
1624
1625 /**
1626 * Something has changed in the phone's state. Update the UI.
1627 */
1628 private void onPhoneStateChanged(AsyncResult r) {
1629 PhoneConstants.State state = mCM.getState();
1630 if (DBG) log("onPhoneStateChanged: current state = " + state);
1631
1632 // There's nothing to do here if we're not the foreground activity.
1633 // (When we *do* eventually come to the foreground, we'll do a
1634 // full update then.)
1635 if (!mIsForegroundActivity) {
1636 if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
1637 return;
1638 }
1639
1640 updateExpandedViewState();
1641
1642 // Update the onscreen UI.
1643 // We use requestUpdateScreen() here (which posts a handler message)
1644 // instead of calling updateScreen() directly, which allows us to avoid
1645 // unnecessary work if multiple onPhoneStateChanged() events come in all
1646 // at the same time.
1647
1648 requestUpdateScreen();
1649
1650 // Make sure we update the poke lock and wake lock when certain
1651 // phone state changes occur.
1652 mApp.updateWakeState();
1653 }
1654
1655 /**
1656 * Updates the UI after a phone connection is disconnected, as follows:
1657 *
1658 * - If this was a missed or rejected incoming call, and no other
1659 * calls are active, dismiss the in-call UI immediately. (The
1660 * CallNotifier will still create a "missed call" notification if
1661 * necessary.)
1662 *
1663 * - With any other disconnect cause, if the phone is now totally
1664 * idle, display the "Call ended" state for a couple of seconds.
1665 *
1666 * - Or, if the phone is still in use, stay on the in-call screen
1667 * (and update the UI to reflect the current state of the Phone.)
1668 *
1669 * @param r r.result contains the connection that just ended
1670 */
1671 private void onDisconnect(AsyncResult r) {
1672 Connection c = (Connection) r.result;
1673 Connection.DisconnectCause cause = c.getDisconnectCause();
1674 if (DBG) log("onDisconnect: connection '" + c + "', cause = " + cause
1675 + ", showing screen: " + mApp.isShowingCallScreen());
1676
1677 boolean currentlyIdle = !phoneIsInUse();
1678 int autoretrySetting = AUTO_RETRY_OFF;
1679 boolean phoneIsCdma = (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
1680 if (phoneIsCdma) {
1681 // Get the Auto-retry setting only if Phone State is IDLE,
1682 // else let it stay as AUTO_RETRY_OFF
1683 if (currentlyIdle) {
1684 autoretrySetting = android.provider.Settings.Global.getInt(mPhone.getContext().
1685 getContentResolver(), android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
1686 }
1687 }
1688
1689 // for OTA Call, only if in OTA NORMAL mode, handle OTA END scenario
1690 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
1691 && ((mApp.cdmaOtaProvisionData != null)
1692 && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
1693 setInCallScreenMode(InCallScreenMode.OTA_ENDED);
1694 updateScreen();
1695 return;
1696 } else if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
1697 || ((mApp.cdmaOtaProvisionData != null)
1698 && mApp.cdmaOtaProvisionData.inOtaSpcState)) {
1699 if (DBG) log("onDisconnect: OTA Call end already handled");
1700 return;
1701 }
1702
1703 // Any time a call disconnects, clear out the "history" of DTMF
1704 // digits you typed (to make sure it doesn't persist from one call
1705 // to the next.)
1706 mDialer.clearDigits();
1707
1708 // Under certain call disconnected states, we want to alert the user
1709 // with a dialog instead of going through the normal disconnect
1710 // routine.
1711 if (cause == Connection.DisconnectCause.CALL_BARRED) {
1712 showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
1713 return;
1714 } else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
1715 showGenericErrorDialog(R.string.callFailed_fdn_only, false);
1716 return;
1717 } else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
1718 showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
1719 return;
1720 } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
1721 showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
1722 return;
1723 } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
1724 showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
1725 return;
1726 }
1727
1728 if (phoneIsCdma) {
1729 Call.State callState = mApp.notifier.getPreviousCdmaCallState();
1730 if ((callState == Call.State.ACTIVE)
1731 && (cause != Connection.DisconnectCause.INCOMING_MISSED)
1732 && (cause != Connection.DisconnectCause.NORMAL)
1733 && (cause != Connection.DisconnectCause.LOCAL)
1734 && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1735 showCallLostDialog();
1736 } else if ((callState == Call.State.DIALING || callState == Call.State.ALERTING)
1737 && (cause != Connection.DisconnectCause.INCOMING_MISSED)
1738 && (cause != Connection.DisconnectCause.NORMAL)
1739 && (cause != Connection.DisconnectCause.LOCAL)
1740 && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
1741
1742 if (mApp.inCallUiState.needToShowCallLostDialog) {
1743 // Show the dialog now since the call that just failed was a retry.
1744 showCallLostDialog();
1745 mApp.inCallUiState.needToShowCallLostDialog = false;
1746 } else {
1747 if (autoretrySetting == AUTO_RETRY_OFF) {
1748 // Show the dialog for failed call if Auto Retry is OFF in Settings.
1749 showCallLostDialog();
1750 mApp.inCallUiState.needToShowCallLostDialog = false;
1751 } else {
1752 // Set the needToShowCallLostDialog flag now, so we'll know to show
1753 // the dialog if *this* call fails.
1754 mApp.inCallUiState.needToShowCallLostDialog = true;
1755 }
1756 }
1757 }
1758 }
1759
1760 // Explicitly clean up up any DISCONNECTED connections
1761 // in a conference call.
1762 // [Background: Even after a connection gets disconnected, its
1763 // Connection object still stays around for a few seconds, in the
1764 // DISCONNECTED state. With regular calls, this state drives the
1765 // "call ended" UI. But when a single person disconnects from a
1766 // conference call there's no "call ended" state at all; in that
1767 // case we blow away any DISCONNECTED connections right now to make sure
1768 // the UI updates instantly to reflect the current state.]
1769 final Call call = c.getCall();
1770 if (call != null) {
1771 // We only care about situation of a single caller
1772 // disconnecting from a conference call. In that case, the
1773 // call will have more than one Connection (including the one
1774 // that just disconnected, which will be in the DISCONNECTED
1775 // state) *and* at least one ACTIVE connection. (If the Call
1776 // has *no* ACTIVE connections, that means that the entire
1777 // conference call just ended, so we *do* want to show the
1778 // "Call ended" state.)
1779 List<Connection> connections = call.getConnections();
1780 if (connections != null && connections.size() > 1) {
1781 for (Connection conn : connections) {
1782 if (conn.getState() == Call.State.ACTIVE) {
1783 // This call still has at least one ACTIVE connection!
1784 // So blow away any DISCONNECTED connections
1785 // (including, presumably, the one that just
1786 // disconnected from this conference call.)
1787
1788 // We also force the wake state to refresh, just in
1789 // case the disconnected connections are removed
1790 // before the phone state change.
1791 if (VDBG) log("- Still-active conf call; clearing DISCONNECTED...");
1792 mApp.updateWakeState();
1793 mCM.clearDisconnected(); // This happens synchronously.
1794 break;
1795 }
1796 }
1797 }
1798 }
1799
1800 // Note: see CallNotifier.onDisconnect() for some other behavior
1801 // that might be triggered by a disconnect event, like playing the
1802 // busy/congestion tone.
1803
1804 // Stash away some info about the call that just disconnected.
1805 // (This might affect what happens after we exit the InCallScreen; see
1806 // delayedCleanupAfterDisconnect().)
1807 // TODO: rather than stashing this away now and then reading it in
1808 // delayedCleanupAfterDisconnect(), it would be cleaner to just pass
1809 // this as an argument to delayedCleanupAfterDisconnect() (if we call
1810 // it directly) or else pass it as a Message argument when we post the
1811 // DELAYED_CLEANUP_AFTER_DISCONNECT message.
1812 mLastDisconnectCause = cause;
1813
1814 // We bail out immediately (and *don't* display the "call ended"
1815 // state at all) if this was an incoming call.
1816 boolean bailOutImmediately =
1817 ((cause == Connection.DisconnectCause.INCOMING_MISSED)
1818 || (cause == Connection.DisconnectCause.INCOMING_REJECTED))
1819 && currentlyIdle;
1820
1821 boolean showingQuickResponseDialog =
1822 mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
1823
1824 // Note: we also do some special handling for the case when a call
1825 // disconnects with cause==OUT_OF_SERVICE while making an
1826 // emergency call from airplane mode. That's handled by
1827 // EmergencyCallHelper.onDisconnect().
1828
1829 if (bailOutImmediately && showingQuickResponseDialog) {
1830 if (DBG) log("- onDisconnect: Respond-via-SMS dialog is still being displayed...");
1831
1832 // Do *not* exit the in-call UI yet!
1833 // If the call was an incoming call that was missed *and* the user is using
1834 // quick response screen, we keep showing the screen for a moment, assuming the
1835 // user wants to reply the call anyway.
1836 //
1837 // For this case, we will exit the screen when:
1838 // - the message is sent (RespondViaSmsManager)
1839 // - the message is canceled (RespondViaSmsManager), or
1840 // - when the whole in-call UI becomes background (onPause())
1841 } else if (bailOutImmediately) {
1842 if (DBG) log("- onDisconnect: bailOutImmediately...");
1843
1844 // Exit the in-call UI!
1845 // (This is basically the same "delayed cleanup" we do below,
1846 // just with zero delay. Since the Phone is currently idle,
1847 // this call is guaranteed to immediately finish this activity.)
1848 delayedCleanupAfterDisconnect();
1849 } else {
1850 if (DBG) log("- onDisconnect: delayed bailout...");
1851 // Stay on the in-call screen for now. (Either the phone is
1852 // still in use, or the phone is idle but we want to display
1853 // the "call ended" state for a couple of seconds.)
1854
1855 // Switch to the special "Call ended" state when the phone is idle
1856 // but there's still a call in the DISCONNECTED state:
1857 if (currentlyIdle
1858 && (mCM.hasDisconnectedFgCall() || mCM.hasDisconnectedBgCall())) {
1859 if (DBG) log("- onDisconnect: switching to 'Call ended' state...");
1860 setInCallScreenMode(InCallScreenMode.CALL_ENDED);
1861 }
1862
1863 // Force a UI update in case we need to display anything
1864 // special based on this connection's DisconnectCause
1865 // (see CallCard.getCallFailedString()).
1866 updateScreen();
1867
1868 // Some other misc cleanup that we do if the call that just
1869 // disconnected was the foreground call.
1870 final boolean hasActiveCall = mCM.hasActiveFgCall();
1871 if (!hasActiveCall) {
1872 if (DBG) log("- onDisconnect: cleaning up after FG call disconnect...");
1873
1874 // Dismiss any dialogs which are only meaningful for an
1875 // active call *and* which become moot if the call ends.
1876 if (mWaitPromptDialog != null) {
1877 if (VDBG) log("- DISMISSING mWaitPromptDialog.");
1878 mWaitPromptDialog.dismiss(); // safe even if already dismissed
1879 mWaitPromptDialog = null;
1880 }
1881 if (mWildPromptDialog != null) {
1882 if (VDBG) log("- DISMISSING mWildPromptDialog.");
1883 mWildPromptDialog.dismiss(); // safe even if already dismissed
1884 mWildPromptDialog = null;
1885 }
1886 if (mPausePromptDialog != null) {
1887 if (DBG) log("- DISMISSING mPausePromptDialog.");
1888 mPausePromptDialog.dismiss(); // safe even if already dismissed
1889 mPausePromptDialog = null;
1890 }
1891 }
1892
1893 // Updating the screen wake state is done in onPhoneStateChanged().
1894
1895
1896 // CDMA: We only clean up if the Phone state is IDLE as we might receive an
1897 // onDisconnect for a Call Collision case (rare but possible).
1898 // For Call collision cases i.e. when the user makes an out going call
1899 // and at the same time receives an Incoming Call, the Incoming Call is given
1900 // higher preference. At this time framework sends a disconnect for the Out going
1901 // call connection hence we should *not* bring down the InCallScreen as the Phone
1902 // State would be RINGING
1903 if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
1904 if (!currentlyIdle) {
1905 // Clean up any connections in the DISCONNECTED state.
1906 // This is necessary cause in CallCollision the foreground call might have
1907 // connections in DISCONNECTED state which needs to be cleared.
1908 mCM.clearDisconnected();
1909
1910 // The phone is still in use. Stay here in this activity.
1911 // But we don't need to keep the screen on.
1912 if (DBG) log("onDisconnect: Call Collision case - staying on InCallScreen.");
1913 if (DBG) PhoneUtils.dumpCallState(mPhone);
1914 return;
1915 }
1916 }
1917
1918 // This is onDisconnect() request from the last phone call; no available call anymore.
1919 //
1920 // When the in-call UI is in background *because* the screen is turned off (unlike the
1921 // other case where the other activity is being shown), we wake up the screen and
1922 // show "DISCONNECTED" state once, with appropriate elapsed time. After showing that
1923 // we *must* bail out of the screen again, showing screen lock if needed.
1924 //
1925 // See also comments for isForegroundActivityForProximity()
1926 //
1927 // TODO: Consider moving this to CallNotifier. This code assumes the InCallScreen
1928 // never gets destroyed. For this exact case, it works (since InCallScreen won't be
1929 // destroyed), while technically this isn't right; Activity may be destroyed when
1930 // in background.
1931 if (currentlyIdle && !isForegroundActivity() && isForegroundActivityForProximity()) {
1932 log("Force waking up the screen to let users see \"disconnected\" state");
1933 if (call != null) {
1934 mCallCard.updateElapsedTimeWidget(call);
1935 }
1936 // This variable will be kept true until the next InCallScreen#onPause(), which
1937 // forcibly turns it off regardless of the situation (for avoiding unnecessary
1938 // confusion around this special case).
1939 mApp.inCallUiState.showAlreadyDisconnectedState = true;
1940
1941 // Finally request wake-up..
1942 mApp.wakeUpScreen();
1943
1944 // InCallScreen#onResume() will set DELAYED_CLEANUP_AFTER_DISCONNECT message,
1945 // so skip the following section.
1946 return;
1947 }
1948
1949 // Finally, arrange for delayedCleanupAfterDisconnect() to get
1950 // called after a short interval (during which we display the
1951 // "call ended" state.) At that point, if the
1952 // Phone is idle, we'll finish out of this activity.
1953 final int callEndedDisplayDelay;
1954 switch (cause) {
1955 // When the local user hanged up the ongoing call, it is ok to dismiss the screen
1956 // soon. In other cases, we show the "hung up" screen longer.
1957 //
1958 // - For expected reasons we will use CALL_ENDED_LONG_DELAY.
1959 // -- when the peer hanged up the call
1960 // -- when the local user rejects the incoming call during the other ongoing call
1961 // (TODO: there may be other cases which should be in this category)
1962 //
1963 // - For other unexpected reasons, we will use CALL_ENDED_EXTRA_LONG_DELAY,
1964 // assuming the local user wants to confirm the disconnect reason.
1965 case LOCAL:
1966 callEndedDisplayDelay = CALL_ENDED_SHORT_DELAY;
1967 break;
1968 case NORMAL:
1969 case INCOMING_REJECTED:
1970 callEndedDisplayDelay = CALL_ENDED_LONG_DELAY;
1971 break;
1972 default:
1973 callEndedDisplayDelay = CALL_ENDED_EXTRA_LONG_DELAY;
1974 break;
1975 }
1976 mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
1977 mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
1978 callEndedDisplayDelay);
1979 }
1980
1981 // Remove 3way timer (only meaningful for CDMA)
1982 // TODO: this call needs to happen in the CallController, not here.
1983 // (It should probably be triggered by the CallNotifier's onDisconnect method.)
1984 // mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
1985 }
1986
1987 /**
1988 * Brings up the "MMI Started" dialog.
1989 */
1990 /* TODO: sort out MMI code (probably we should remove this method entirely). See also
1991 MMI handling code in onResume()
1992 private void onMMIInitiate(AsyncResult r) {
1993 if (VDBG) log("onMMIInitiate()... AsyncResult r = " + r);
1994
1995 // Watch out: don't do this if we're not the foreground activity,
1996 // mainly since in the Dialog.show() might fail if we don't have a
1997 // valid window token any more...
1998 // (Note that this exact sequence can happen if you try to start
1999 // an MMI code while the radio is off or out of service.)
2000 if (!mIsForegroundActivity) {
2001 if (VDBG) log("Activity not in foreground! Bailing out...");
2002 return;
2003 }
2004
2005 // Also, if any other dialog is up right now (presumably the
2006 // generic error dialog displaying the "Starting MMI..." message)
2007 // take it down before bringing up the real "MMI Started" dialog
2008 // in its place.
2009 dismissAllDialogs();
2010
2011 MmiCode mmiCode = (MmiCode) r.result;
2012 if (VDBG) log(" - MmiCode: " + mmiCode);
2013
2014 Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
2015 mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
2016 message, mMmiStartedDialog);
2017 }*/
2018
2019 /**
2020 * Handles an MMI_CANCEL event, which is triggered by the button
2021 * (labeled either "OK" or "Cancel") on the "MMI Started" dialog.
2022 * @see PhoneUtils#cancelMmiCode(Phone)
2023 */
2024 private void onMMICancel() {
2025 if (VDBG) log("onMMICancel()...");
2026
2027 // First of all, cancel the outstanding MMI code (if possible.)
2028 PhoneUtils.cancelMmiCode(mPhone);
2029
2030 // Regardless of whether the current MMI code was cancelable, the
2031 // PhoneApp will get an MMI_COMPLETE event very soon, which will
2032 // take us to the MMI Complete dialog (see
2033 // PhoneUtils.displayMMIComplete().)
2034 //
2035 // But until that event comes in, we *don't* want to stay here on
2036 // the in-call screen, since we'll be visible in a
2037 // partially-constructed state as soon as the "MMI Started" dialog
2038 // gets dismissed. So let's forcibly bail out right now.
2039 if (DBG) log("onMMICancel: finishing InCallScreen...");
2040 dismissAllDialogs();
2041 endInCallScreenSession();
2042 }
2043
2044 /**
2045 * Handles an MMI_COMPLETE event, which is triggered by telephony,
2046 * implying MMI
2047 */
2048 private void onMMIComplete(MmiCode mmiCode) {
2049 // Check the code to see if the request is ready to
2050 // finish, this includes any MMI state that is not
2051 // PENDING.
2052
2053 // if phone is a CDMA phone display feature code completed message
2054 int phoneType = mPhone.getPhoneType();
2055 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2056 PhoneUtils.displayMMIComplete(mPhone, mApp, mmiCode, null, null);
2057 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
2058 if (mmiCode.getState() != MmiCode.State.PENDING) {
2059 if (DBG) log("Got MMI_COMPLETE, finishing InCallScreen...");
2060 dismissAllDialogs();
2061 endInCallScreenSession();
2062 }
2063 }
2064 }
2065
2066 /**
2067 * Handles the POST_ON_DIAL_CHARS message from the Phone
2068 * (see our call to mPhone.setOnPostDialCharacter() above.)
2069 *
2070 * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle
2071 * "dialable" key events here in the InCallScreen: we do directly to the
2072 * Dialer UI instead. Similarly, we may now need to go directly to the
2073 * Dialer to handle POST_ON_DIAL_CHARS too.
2074 */
2075 private void handlePostOnDialChars(AsyncResult r, char ch) {
2076 Connection c = (Connection) r.result;
2077
2078 if (c != null) {
2079 Connection.PostDialState state =
2080 (Connection.PostDialState) r.userObj;
2081
2082 if (VDBG) log("handlePostOnDialChar: state = " +
2083 state + ", ch = " + ch);
2084
2085 switch (state) {
2086 case STARTED:
2087 mDialer.stopLocalToneIfNeeded();
2088 if (mPauseInProgress) {
2089 /**
2090 * Note that on some devices, this will never happen,
2091 * because we will not ever enter the PAUSE state.
2092 */
2093 showPausePromptDialog(c, mPostDialStrAfterPause);
2094 }
2095 mPauseInProgress = false;
2096 mDialer.startLocalToneIfNeeded(ch);
2097
2098 // TODO: is this needed, now that you can't actually
2099 // type DTMF chars or dial directly from here?
2100 // If so, we'd need to yank you out of the in-call screen
2101 // here too (and take you to the 12-key dialer in "in-call" mode.)
2102 // displayPostDialedChar(ch);
2103 break;
2104
2105 case WAIT:
2106 // wait shows a prompt.
2107 if (DBG) log("handlePostOnDialChars: show WAIT prompt...");
2108 mDialer.stopLocalToneIfNeeded();
2109 String postDialStr = c.getRemainingPostDialString();
2110 showWaitPromptDialog(c, postDialStr);
2111 break;
2112
2113 case WILD:
2114 if (DBG) log("handlePostOnDialChars: show WILD prompt");
2115 mDialer.stopLocalToneIfNeeded();
2116 showWildPromptDialog(c);
2117 break;
2118
2119 case COMPLETE:
2120 mDialer.stopLocalToneIfNeeded();
2121 break;
2122
2123 case PAUSE:
2124 // pauses for a brief period of time then continue dialing.
2125 mDialer.stopLocalToneIfNeeded();
2126 mPostDialStrAfterPause = c.getRemainingPostDialString();
2127 mPauseInProgress = true;
2128 break;
2129
2130 default:
2131 break;
2132 }
2133 }
2134 }
2135
2136 /**
2137 * Pop up an alert dialog with OK and Cancel buttons to allow user to
2138 * Accept or Reject the WAIT inserted as part of the Dial string.
2139 */
2140 private void showWaitPromptDialog(final Connection c, String postDialStr) {
2141 if (DBG) log("showWaitPromptDialogChoice: '" + postDialStr + "'...");
2142
2143 Resources r = getResources();
2144 StringBuilder buf = new StringBuilder();
2145 buf.append(r.getText(R.string.wait_prompt_str));
2146 buf.append(postDialStr);
2147
2148 // if (DBG) log("- mWaitPromptDialog = " + mWaitPromptDialog);
2149 if (mWaitPromptDialog != null) {
2150 if (DBG) log("- DISMISSING mWaitPromptDialog.");
2151 mWaitPromptDialog.dismiss(); // safe even if already dismissed
2152 mWaitPromptDialog = null;
2153 }
2154
2155 mWaitPromptDialog = new AlertDialog.Builder(this)
2156 .setMessage(buf.toString())
2157 .setPositiveButton(R.string.pause_prompt_yes,
2158 new DialogInterface.OnClickListener() {
2159 @Override
2160 public void onClick(DialogInterface dialog, int whichButton) {
2161 if (DBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
2162 c.proceedAfterWaitChar();
2163 }
2164 })
2165 .setNegativeButton(R.string.pause_prompt_no,
2166 new DialogInterface.OnClickListener() {
2167 @Override
2168 public void onClick(DialogInterface dialog, int whichButton) {
2169 if (DBG) log("handle POST_DIAL_CANCELED!");
2170 c.cancelPostDial();
2171 }
2172 })
2173 .create();
2174 mWaitPromptDialog.getWindow().addFlags(
2175 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2176
2177 mWaitPromptDialog.show();
2178 }
2179
2180 /**
2181 * Pop up an alert dialog which waits for 2 seconds for each P (Pause) Character entered
2182 * as part of the Dial String.
2183 */
2184 private void showPausePromptDialog(final Connection c, String postDialStrAfterPause) {
2185 Resources r = getResources();
2186 StringBuilder buf = new StringBuilder();
2187 buf.append(r.getText(R.string.pause_prompt_str));
2188 buf.append(postDialStrAfterPause);
2189
2190 if (mPausePromptDialog != null) {
2191 if (DBG) log("- DISMISSING mPausePromptDialog.");
2192 mPausePromptDialog.dismiss(); // safe even if already dismissed
2193 mPausePromptDialog = null;
2194 }
2195
2196 mPausePromptDialog = new AlertDialog.Builder(this)
2197 .setMessage(buf.toString())
2198 .create();
2199 mPausePromptDialog.show();
2200 // 2 second timer
2201 Message msg = Message.obtain(mHandler, EVENT_PAUSE_DIALOG_COMPLETE);
2202 mHandler.sendMessageDelayed(msg, PAUSE_PROMPT_DIALOG_TIMEOUT);
2203 }
2204
2205 private View createWildPromptView() {
2206 LinearLayout result = new LinearLayout(this);
2207 result.setOrientation(LinearLayout.VERTICAL);
2208 result.setPadding(5, 5, 5, 5);
2209
2210 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
2211 ViewGroup.LayoutParams.MATCH_PARENT,
2212 ViewGroup.LayoutParams.WRAP_CONTENT);
2213
2214 TextView promptMsg = new TextView(this);
2215 promptMsg.setTextSize(14);
2216 promptMsg.setTypeface(Typeface.DEFAULT_BOLD);
2217 promptMsg.setText(getResources().getText(R.string.wild_prompt_str));
2218
2219 result.addView(promptMsg, lp);
2220
2221 mWildPromptText = new EditText(this);
2222 mWildPromptText.setKeyListener(DialerKeyListener.getInstance());
2223 mWildPromptText.setMovementMethod(null);
2224 mWildPromptText.setTextSize(14);
2225 mWildPromptText.setMaxLines(1);
2226 mWildPromptText.setHorizontallyScrolling(true);
2227 mWildPromptText.setBackgroundResource(android.R.drawable.editbox_background);
2228
2229 LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
2230 ViewGroup.LayoutParams.MATCH_PARENT,
2231 ViewGroup.LayoutParams.WRAP_CONTENT);
2232 lp2.setMargins(0, 3, 0, 0);
2233
2234 result.addView(mWildPromptText, lp2);
2235
2236 return result;
2237 }
2238
2239 private void showWildPromptDialog(final Connection c) {
2240 View v = createWildPromptView();
2241
2242 if (mWildPromptDialog != null) {
2243 if (VDBG) log("- DISMISSING mWildPromptDialog.");
2244 mWildPromptDialog.dismiss(); // safe even if already dismissed
2245 mWildPromptDialog = null;
2246 }
2247
2248 mWildPromptDialog = new AlertDialog.Builder(this)
2249 .setView(v)
2250 .setPositiveButton(
2251 R.string.send_button,
2252 new DialogInterface.OnClickListener() {
2253 @Override
2254 public void onClick(DialogInterface dialog, int whichButton) {
2255 if (VDBG) log("handle WILD_PROMPT_CHAR_ENTERED, proceed...");
2256 String replacement = null;
2257 if (mWildPromptText != null) {
2258 replacement = mWildPromptText.getText().toString();
2259 mWildPromptText = null;
2260 }
2261 c.proceedAfterWildChar(replacement);
2262 mApp.pokeUserActivity();
2263 }
2264 })
2265 .setOnCancelListener(
2266 new DialogInterface.OnCancelListener() {
2267 @Override
2268 public void onCancel(DialogInterface dialog) {
2269 if (VDBG) log("handle POST_DIAL_CANCELED!");
2270 c.cancelPostDial();
2271 mApp.pokeUserActivity();
2272 }
2273 })
2274 .create();
2275 mWildPromptDialog.getWindow().addFlags(
2276 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
2277 mWildPromptDialog.show();
2278
2279 mWildPromptText.requestFocus();
2280 }
2281
2282 /**
2283 * Updates the state of the in-call UI based on the current state of
2284 * the Phone. This call has no effect if we're not currently the
2285 * foreground activity.
2286 *
2287 * This method is only allowed to be called from the UI thread (since it
2288 * manipulates our View hierarchy). If you need to update the screen from
2289 * some other thread, or if you just want to "post a request" for the screen
2290 * to be updated (rather than doing it synchronously), call
2291 * requestUpdateScreen() instead.
2292 *
2293 * Right now this method will update UI visibility immediately, with no animation.
2294 * TODO: have animate flag here and use it anywhere possible.
2295 */
2296 private void updateScreen() {
2297 if (DBG) log("updateScreen()...");
2298 final InCallScreenMode inCallScreenMode = mApp.inCallUiState.inCallScreenMode;
2299 if (VDBG) {
2300 PhoneConstants.State state = mCM.getState();
2301 log(" - phone state = " + state);
2302 log(" - inCallScreenMode = " + inCallScreenMode);
2303 }
2304
2305 // Don't update anything if we're not in the foreground (there's
2306 // no point updating our UI widgets since we're not visible!)
2307 // Also note this check also ensures we won't update while we're
2308 // in the middle of pausing, which could cause a visible glitch in
2309 // the "activity ending" transition.
2310 if (!mIsForegroundActivity) {
2311 if (DBG) log("- updateScreen: not the foreground Activity! Bailing out...");
2312 return;
2313 }
2314
2315 if (inCallScreenMode == InCallScreenMode.OTA_NORMAL) {
2316 if (DBG) log("- updateScreen: OTA call state NORMAL (NOT updating in-call UI)...");
2317 mCallCard.setVisibility(View.GONE);
2318 if (mApp.otaUtils != null) {
2319 mApp.otaUtils.otaShowProperScreen();
2320 } else {
2321 Log.w(LOG_TAG, "OtaUtils object is null, not showing any screen for that.");
2322 }
2323 return; // Return without updating in-call UI.
2324 } else if (inCallScreenMode == InCallScreenMode.OTA_ENDED) {
2325 if (DBG) log("- updateScreen: OTA call ended state (NOT updating in-call UI)...");
2326 mCallCard.setVisibility(View.GONE);
2327 // Wake up the screen when we get notification, good or bad.
2328 mApp.wakeUpScreen();
2329 if (mApp.cdmaOtaScreenState.otaScreenState
2330 == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION) {
2331 if (DBG) log("- updateScreen: OTA_STATUS_ACTIVATION");
2332 if (mApp.otaUtils != null) {
2333 if (DBG) log("- updateScreen: mApp.otaUtils is not null, "
2334 + "call otaShowActivationScreen");
2335 mApp.otaUtils.otaShowActivateScreen();
2336 }
2337 } else {
2338 if (DBG) log("- updateScreen: OTA Call end state for Dialogs");
2339 if (mApp.otaUtils != null) {
2340 if (DBG) log("- updateScreen: Show OTA Success Failure dialog");
2341 mApp.otaUtils.otaShowSuccessFailure();
2342 }
2343 }
2344 return; // Return without updating in-call UI.
2345 } else if (inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
2346 if (DBG) log("- updateScreen: manage conference mode (NOT updating in-call UI)...");
2347 mCallCard.setVisibility(View.GONE);
2348 updateManageConferencePanelIfNecessary();
2349 return; // Return without updating in-call UI.
2350 } else if (inCallScreenMode == InCallScreenMode.CALL_ENDED) {
2351 if (DBG) log("- updateScreen: call ended state...");
2352 // Continue with the rest of updateScreen() as usual, since we do
2353 // need to update the background (to the special "call ended" color)
2354 // and the CallCard (to show the "Call ended" label.)
2355 }
2356
2357 if (DBG) log("- updateScreen: updating the in-call UI...");
2358 // Note we update the InCallTouchUi widget before the CallCard,
2359 // since the CallCard adjusts its size based on how much vertical
2360 // space the InCallTouchUi widget needs.
2361 updateInCallTouchUi();
2362 mCallCard.updateState(mCM);
2363
2364 // If an incoming call is ringing, make sure the dialpad is
2365 // closed. (We do this to make sure we're not covering up the
2366 // "incoming call" UI.)
2367 if (mCM.getState() == PhoneConstants.State.RINGING) {
2368 if (mDialer.isOpened()) {
2369 Log.i(LOG_TAG, "During RINGING state we force hiding dialpad.");
2370 closeDialpadInternal(false); // don't do the "closing" animation
2371 }
2372
2373 // At this point, we are guranteed that the dialer is closed.
2374 // This means that it is safe to clear out the "history" of DTMF digits
2375 // you may have typed into the previous call (so you don't see the
2376 // previous call's digits if you answer this call and then bring up the
2377 // dialpad.)
2378 //
2379 // TODO: it would be more precise to do this when you *answer* the
2380 // incoming call, rather than as soon as it starts ringing, but
2381 // the InCallScreen doesn't keep enough state right now to notice
2382 // that specific transition in onPhoneStateChanged().
2383 // TODO: This clears out the dialpad context as well so when a second
2384 // call comes in while a voicemail call is happening, the voicemail
2385 // dialpad will no longer have the "Voice Mail" context. It's a small
2386 // case so not terribly bad, but we need to maintain a better
2387 // call-to-callstate mapping before we can fix this.
2388 mDialer.clearDigits();
2389 }
2390
2391
2392 // Now that we're sure DTMF dialpad is in an appropriate state, reflect
2393 // the dialpad state into CallCard
2394 updateCallCardVisibilityPerDialerState(false);
2395
2396 updateProgressIndication();
2397
2398 // Forcibly take down all dialog if an incoming call is ringing.
2399 if (mCM.hasActiveRingingCall()) {
2400 dismissAllDialogs();
2401 } else {
2402 // Wait prompt dialog is not currently up. But it *should* be
2403 // up if the FG call has a connection in the WAIT state and
2404 // the phone isn't ringing.
2405 String postDialStr = null;
2406 List<Connection> fgConnections = mCM.getFgCallConnections();
2407 int phoneType = mCM.getFgPhone().getPhoneType();
2408 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
2409 Connection fgLatestConnection = mCM.getFgCallLatestConnection();
2410 if (mApp.cdmaPhoneCallState.getCurrentCallState() ==
2411 CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
2412 for (Connection cn : fgConnections) {
2413 if ((cn != null) && (cn.getPostDialState() ==
2414 Connection.PostDialState.WAIT)) {
2415 cn.cancelPostDial();
2416 }
2417 }
2418 } else if ((fgLatestConnection != null)
2419 && (fgLatestConnection.getPostDialState() == Connection.PostDialState.WAIT)) {
2420 if(DBG) log("show the Wait dialog for CDMA");
2421 postDialStr = fgLatestConnection.getRemainingPostDialString();
2422 showWaitPromptDialog(fgLatestConnection, postDialStr);
2423 }
2424 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
2425 || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
2426 for (Connection cn : fgConnections) {
2427 if ((cn != null) && (cn.getPostDialState() == Connection.PostDialState.WAIT)) {
2428 postDialStr = cn.getRemainingPostDialString();
2429 showWaitPromptDialog(cn, postDialStr);
2430 }
2431 }
2432 } else {
2433 throw new IllegalStateException("Unexpected phone type: " + phoneType);
2434 }
2435 }
2436 }
2437
2438 /**
2439 * (Re)synchronizes the onscreen UI with the current state of the
2440 * telephony framework.
2441 *
2442 * @return SyncWithPhoneStateStatus.SUCCESS if we successfully updated the UI, or
2443 * SyncWithPhoneStateStatus.PHONE_NOT_IN_USE if there was no phone state to sync
2444 * with (ie. the phone was completely idle). In the latter case, we
2445 * shouldn't even be in the in-call UI in the first place, and it's
2446 * the caller's responsibility to bail out of this activity by
2447 * calling endInCallScreenSession if appropriate.
2448 *
2449 * This method directly calls updateScreen() in the normal "phone is
2450 * in use" case, so there's no need for the caller to do so.
2451 */
2452 private SyncWithPhoneStateStatus syncWithPhoneState() {
2453 boolean updateSuccessful = false;
2454 if (DBG) log("syncWithPhoneState()...");
2455 if (DBG) PhoneUtils.dumpCallState(mPhone);
2456 if (VDBG) dumpBluetoothState();
2457
2458 // Make sure the Phone is "in use". (If not, we shouldn't be on
2459 // this screen in the first place.)
2460
2461 // An active or just-ended OTA call counts as "in use".
2462 if (TelephonyCapabilities.supportsOtasp(mCM.getFgPhone())
2463 && ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
2464 || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED))) {
2465 // Even when OTA Call ends, need to show OTA End UI,
2466 // so return Success to allow UI update.
2467 return SyncWithPhoneStateStatus.SUCCESS;
2468 }
2469
2470 // If an MMI code is running that also counts as "in use".
2471 //
2472 // TODO: We currently only call getPendingMmiCodes() for GSM
2473 // phones. (The code's been that way all along.) But CDMAPhone
2474 // does in fact implement getPendingMmiCodes(), so should we
2475 // check that here regardless of the phone type?
2476 boolean hasPendingMmiCodes =
2477 (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
2478 && !mPhone.getPendingMmiCodes().isEmpty();
2479
2480 // Finally, it's also OK to stay here on the InCallScreen if we
2481 // need to display a progress indicator while something's
2482 // happening in the background.
2483 boolean showProgressIndication = mApp.inCallUiState.isProgressIndicationActive();
2484
2485 boolean showScreenEvenAfterDisconnect = mApp.inCallUiState.showAlreadyDisconnectedState;
2486
2487 if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()
2488 || hasPendingMmiCodes || showProgressIndication || showScreenEvenAfterDisconnect) {
2489 if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");
2490 updateScreen();
2491 return SyncWithPhoneStateStatus.SUCCESS;
2492 }
2493
2494 Log.i(LOG_TAG, "syncWithPhoneState: phone is idle (shouldn't be here)");
2495 return SyncWithPhoneStateStatus.PHONE_NOT_IN_USE;
2496 }
2497
2498
2499
2500 private void handleMissingVoiceMailNumber() {
2501 if (DBG) log("handleMissingVoiceMailNumber");
2502
2503 final Message msg = Message.obtain(mHandler);
2504 msg.what = DONT_ADD_VOICEMAIL_NUMBER;
2505
2506 final Message msg2 = Message.obtain(mHandler);
2507 msg2.what = ADD_VOICEMAIL_NUMBER;
2508
2509 mMissingVoicemailDialog = new AlertDialog.Builder(this)
2510 .setTitle(R.string.no_vm_number)
2511 .setMessage(R.string.no_vm_number_msg)
2512 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2513 public void onClick(DialogInterface dialog, int which) {
2514 if (VDBG) log("Missing voicemail AlertDialog: POSITIVE click...");
2515 msg.sendToTarget(); // see dontAddVoiceMailNumber()
2516 mApp.pokeUserActivity();
2517 }})
2518 .setNegativeButton(R.string.add_vm_number_str,
2519 new DialogInterface.OnClickListener() {
2520 public void onClick(DialogInterface dialog, int which) {
2521 if (VDBG) log("Missing voicemail AlertDialog: NEGATIVE click...");
2522 msg2.sendToTarget(); // see addVoiceMailNumber()
2523 mApp.pokeUserActivity();
2524 }})
2525 .setOnCancelListener(new OnCancelListener() {
2526 public void onCancel(DialogInterface dialog) {
2527 if (VDBG) log("Missing voicemail AlertDialog: CANCEL handler...");
2528 msg.sendToTarget(); // see dontAddVoiceMailNumber()
2529 mApp.pokeUserActivity();
2530 }})
2531 .create();
2532
2533 // When the dialog is up, completely hide the in-call UI
2534 // underneath (which is in a partially-constructed state).
2535 mMissingVoicemailDialog.getWindow().addFlags(
2536 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
2537
2538 mMissingVoicemailDialog.show();
2539 }
2540
2541 private void addVoiceMailNumberPanel() {
2542 if (mMissingVoicemailDialog != null) {
2543 mMissingVoicemailDialog.dismiss();
2544 mMissingVoicemailDialog = null;
2545 }
2546 if (DBG) log("addVoiceMailNumberPanel: finishing InCallScreen...");
2547 endInCallScreenSession();
2548
2549 if (DBG) log("show vm setting");
2550
2551 // navigate to the Voicemail setting in the Call Settings activity.
2552 Intent intent = new Intent(CallFeaturesSetting.ACTION_ADD_VOICEMAIL);
2553 intent.setClass(this, CallFeaturesSetting.class);
2554 startActivity(intent);
2555 }
2556
2557 private void dontAddVoiceMailNumber() {
2558 if (mMissingVoicemailDialog != null) {
2559 mMissingVoicemailDialog.dismiss();
2560 mMissingVoicemailDialog = null;
2561 }
2562 if (DBG) log("dontAddVoiceMailNumber: finishing InCallScreen...");
2563 endInCallScreenSession();
2564 }
2565
2566 /**
2567 * Do some delayed cleanup after a Phone call gets disconnected.
2568 *
2569 * This method gets called a couple of seconds after any DISCONNECT
2570 * event from the Phone; it's triggered by the
2571 * DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
2572 *
2573 * If the Phone is totally idle right now, that means we've already
2574 * shown the "call ended" state for a couple of seconds, and it's now
2575 * time to endInCallScreenSession this activity.
2576 *
2577 * If the Phone is *not* idle right now, that probably means that one
2578 * call ended but the other line is still in use. In that case, do
2579 * nothing, and instead stay here on the InCallScreen.
2580 */
2581 private void delayedCleanupAfterDisconnect() {
2582 if (VDBG) log("delayedCleanupAfterDisconnect()... Phone state = " + mCM.getState());
2583
2584 // Clean up any connections in the DISCONNECTED state.
2585 //
2586 // [Background: Even after a connection gets disconnected, its
2587 // Connection object still stays around, in the special
2588 // DISCONNECTED state. This is necessary because we we need the
2589 // caller-id information from that Connection to properly draw the
2590 // "Call ended" state of the CallCard.
2591 // But at this point we truly don't need that connection any
2592 // more, so tell the Phone that it's now OK to to clean up any
2593 // connections still in that state.]
2594 mCM.clearDisconnected();
2595
2596 // There are two cases where we should *not* exit the InCallScreen:
2597 // (1) Phone is still in use
2598 // or
2599 // (2) There's an active progress indication (i.e. the "Retrying..."
2600 // progress dialog) that we need to continue to display.
2601
2602 boolean stayHere = phoneIsInUse() || mApp.inCallUiState.isProgressIndicationActive();
2603
2604 if (stayHere) {
2605 if (DBG) log("- delayedCleanupAfterDisconnect: staying on the InCallScreen...");
2606 } else {
2607 // Phone is idle! We should exit the in-call UI now.
2608 if (DBG) log("- delayedCleanupAfterDisconnect: phone is idle...");
2609
2610 // And (finally!) exit from the in-call screen
2611 // (but not if we're already in the process of pausing...)
2612 if (mIsForegroundActivity) {
2613 if (DBG) log("- delayedCleanupAfterDisconnect: finishing InCallScreen...");
2614
2615 // In some cases we finish the call by taking the user to the
2616 // Call Log. Otherwise, we simply call endInCallScreenSession,
2617 // which will take us back to wherever we came from.
2618 //
2619 // UI note: In eclair and earlier, we went to the Call Log
2620 // after outgoing calls initiated on the device, but never for
2621 // incoming calls. Now we do it for incoming calls too, as
2622 // long as the call was answered by the user. (We always go
2623 // back where you came from after a rejected or missed incoming
2624 // call.)
2625 //
2626 // And in any case, *never* go to the call log if we're in
2627 // emergency mode (i.e. if the screen is locked and a lock
2628 // pattern or PIN/password is set), or if we somehow got here
2629 // on a non-voice-capable device.
2630
2631 if (VDBG) log("- Post-call behavior:");
2632 if (VDBG) log(" - mLastDisconnectCause = " + mLastDisconnectCause);
2633 if (VDBG) log(" - isPhoneStateRestricted() = " + isPhoneStateRestricted());
2634
2635 // DisconnectCause values in the most common scenarios:
2636 // - INCOMING_MISSED: incoming ringing call times out, or the
2637 // other end hangs up while still ringing
2638 // - INCOMING_REJECTED: user rejects the call while ringing
2639 // - LOCAL: user hung up while a call was active (after
2640 // answering an incoming call, or after making an
2641 // outgoing call)
2642 // - NORMAL: the other end hung up (after answering an incoming
2643 // call, or after making an outgoing call)
2644
2645 if ((mLastDisconnectCause != Connection.DisconnectCause.INCOMING_MISSED)
2646 && (mLastDisconnectCause != Connection.DisconnectCause.INCOMING_REJECTED)
2647 && !isPhoneStateRestricted()
2648 && PhoneGlobals.sVoiceCapable) {
2649 final Intent intent = mApp.createPhoneEndIntentUsingCallOrigin();
2650 ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
2651 R.anim.activity_close_enter, R.anim.activity_close_exit);
2652 if (VDBG) {
2653 log("- Show Call Log (or Dialtacts) after disconnect. Current intent: "
2654 + intent);
2655 }
2656 try {
2657 startActivity(intent, opts.toBundle());
2658 } catch (ActivityNotFoundException e) {
2659 // Don't crash if there's somehow no "Call log" at
2660 // all on this device.
2661 // (This should never happen, though, since we already
2662 // checked PhoneApp.sVoiceCapable above, and any
2663 // voice-capable device surely *should* have a call
2664 // log activity....)
2665 Log.w(LOG_TAG, "delayedCleanupAfterDisconnect: "
2666 + "transition to call log failed; intent = " + intent);
2667 // ...so just return back where we came from....
2668 }
2669 // Even if we did go to the call log, note that we still
2670 // call endInCallScreenSession (below) to make sure we don't
2671 // stay in the activity history.
2672 }
2673
2674 }
2675 endInCallScreenSession();
2676
2677 // Reset the call origin when the session ends and this in-call UI is being finished.
2678 mApp.setLatestActiveCallOrigin(null);
2679 }
2680 }
2681
2682
2683 /**
2684 * View.OnClickListener implementation.
2685 *
2686 * This method handles clicks from UI elements that use the
2687 * InCallScreen itself as their OnClickListener.
2688 *
2689 * Note: Currently this method is used only for a few special buttons:
2690 * - the mButtonManageConferenceDone "Back to call" button
2691 * - the "dim" effect for the secondary call photo in CallCard as the second "swap" button
2692 * - other OTASP-specific buttons managed by OtaUtils.java.
2693 *
2694 * *Most* in-call controls are handled by the handleOnscreenButtonClick() method, via the
2695 * InCallTouchUi widget.
2696 */
2697 @Override
2698 public void onClick(View view) {
2699 int id = view.getId();
2700 if (VDBG) log("onClick(View " + view + ", id " + id + ")...");
2701
2702 switch (id) {
2703 case R.id.manage_done: // mButtonManageConferenceDone
2704 if (VDBG) log("onClick: mButtonManageConferenceDone...");
2705 // Hide the Manage Conference panel, return to NORMAL mode.
2706 setInCallScreenMode(InCallScreenMode.NORMAL);
2707 requestUpdateScreen();
2708 break;
2709
2710 case R.id.dim_effect_for_secondary_photo:
2711 if (mInCallControlState.canSwap) {
2712 internalSwapCalls();
2713 }
2714 break;
2715
2716 default:
2717 // Presumably one of the OTASP-specific buttons managed by
2718 // OtaUtils.java.
2719 // (TODO: It would be cleaner for the OtaUtils instance itself to
2720 // be the OnClickListener for its own buttons.)
2721
2722 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
2723 || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
2724 && mApp.otaUtils != null) {
2725 mApp.otaUtils.onClickHandler(id);
2726 } else {
2727 // Uh oh: we *should* only receive clicks here from the
2728 // buttons managed by OtaUtils.java, but if we're not in one
2729 // of the special OTASP modes, those buttons shouldn't have
2730 // been visible in the first place.
2731 Log.w(LOG_TAG,
2732 "onClick: unexpected click from ID " + id + " (View = " + view + ")");
2733 }
2734 break;
2735 }
2736
2737 EventLog.writeEvent(EventLogTags.PHONE_UI_BUTTON_CLICK,
2738 (view instanceof TextView) ? ((TextView) view).getText() : "");
2739
2740 // Clicking any onscreen UI element counts as explicit "user activity".
2741 mApp.pokeUserActivity();
2742 }
2743
2744 private void onHoldClick() {
2745 final boolean hasActiveCall = mCM.hasActiveFgCall();
2746 final boolean hasHoldingCall = mCM.hasActiveBgCall();
2747 log("onHoldClick: hasActiveCall = " + hasActiveCall
2748 + ", hasHoldingCall = " + hasHoldingCall);
2749 boolean newHoldState;
2750 boolean holdButtonEnabled;
2751 if (hasActiveCall && !hasHoldingCall) {
2752 // There's only one line in use, and that line is active.
2753 PhoneUtils.switchHoldingAndActive(
2754 mCM.getFirstActiveBgCall()); // Really means "hold" in this state
2755 newHoldState = true;
2756 holdButtonEnabled = true;
2757 } else if (!hasActiveCall && hasHoldingCall) {
2758 // There's only one line in use, and that line is on hold.
2759 PhoneUtils.switchHoldingAndActive(
2760 mCM.getFirstActiveBgCall()); // Really means "unhold" in this state
2761 newHoldState = false;
2762 holdButtonEnabled = true;
2763 } else {
2764 // Either zero or 2 lines are in use; "hold/unhold" is meaningless.
2765 newHoldState = false;
2766 holdButtonEnabled = false;
2767 }
2768 // No need to forcibly update the onscreen UI; just wait for the
2769 // onPhoneStateChanged() callback. (This seems to be responsive
2770 // enough.)
2771
2772 // Also, any time we hold or unhold, force the DTMF dialpad to close.
2773 closeDialpadInternal(true); // do the "closing" animation
2774 }
2775
2776 /**
2777 * Toggles in-call audio between speaker and the built-in earpiece (or
2778 * wired headset.)
2779 */
2780 public void toggleSpeaker() {
2781 // TODO: Turning on the speaker seems to enable the mic
2782 // whether or not the "mute" feature is active!
2783 // Not sure if this is an feature of the telephony API
2784 // that I need to handle specially, or just a bug.
2785 boolean newSpeakerState = !PhoneUtils.isSpeakerOn(this);
2786 log("toggleSpeaker(): newSpeakerState = " + newSpeakerState);
2787
2788 if (newSpeakerState && isBluetoothAvailable() && isBluetoothAudioConnected()) {
2789 disconnectBluetoothAudio();
2790 }
2791 PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
2792
2793 // And update the InCallTouchUi widget (since the "audio mode"
2794 // button might need to change its appearance based on the new
2795 // audio state.)
2796 updateInCallTouchUi();
2797 }
2798
2799 /*
2800 * onMuteClick is called only when there is a foreground call
2801 */
2802 private void onMuteClick() {
2803 boolean newMuteState = !PhoneUtils.getMute();
2804 log("onMuteClick(): newMuteState = " + newMuteState);
2805 PhoneUtils.setMute(newMuteState);
2806 }
2807
2808 /**
2809 * Toggles whether or not to route in-call audio to the bluetooth
2810 * headset, or do nothing (but log a warning) if no bluetooth device
2811 * is actually connected.
2812 *
2813 * TODO: this method is currently unused, but the "audio mode" UI
2814 * design is still in flux so let's keep it around for now.
2815 * (But if we ultimately end up *not* providing any way for the UI to
2816 * simply "toggle bluetooth", we can get rid of this method.)
2817 */
2818 public void toggleBluetooth() {
2819 if (VDBG) log("toggleBluetooth()...");
2820
2821 if (isBluetoothAvailable()) {
2822 // Toggle the bluetooth audio connection state:
2823 if (isBluetoothAudioConnected()) {
2824 disconnectBluetoothAudio();
2825 } else {
2826 // Manually turn the speaker phone off, instead of allowing the
2827 // Bluetooth audio routing to handle it, since there's other
2828 // important state-updating that needs to happen in the
2829 // PhoneUtils.turnOnSpeaker() method.
2830 // (Similarly, whenever the user turns *on* the speaker, we
2831 // manually disconnect the active bluetooth headset;
2832 // see toggleSpeaker() and/or switchInCallAudio().)
2833 if (PhoneUtils.isSpeakerOn(this)) {
2834 PhoneUtils.turnOnSpeaker(this, false, true);
2835 }
2836
2837 connectBluetoothAudio();
2838 }
2839 } else {
2840 // Bluetooth isn't available; the onscreen UI shouldn't have
2841 // allowed this request in the first place!
2842 Log.w(LOG_TAG, "toggleBluetooth(): bluetooth is unavailable");
2843 }
2844
2845 // And update the InCallTouchUi widget (since the "audio mode"
2846 // button might need to change its appearance based on the new
2847 // audio state.)
2848 updateInCallTouchUi();
2849 }
2850
2851 /**
2852 * Switches the current routing of in-call audio between speaker,
2853 * bluetooth, and the built-in earpiece (or wired headset.)
2854 *
2855 * This method is used on devices that provide a single 3-way switch
2856 * for audio routing. For devices that provide separate toggles for
2857 * Speaker and Bluetooth, see toggleBluetooth() and toggleSpeaker().
2858 *
2859 * TODO: UI design is still in flux. If we end up totally
2860 * eliminating the concept of Speaker and Bluetooth toggle buttons,
2861 * we can get rid of toggleBluetooth() and toggleSpeaker().
2862 */
2863 public void switchInCallAudio(InCallAudioMode newMode) {
2864 log("switchInCallAudio: new mode = " + newMode);
2865 switch (newMode) {
2866 case SPEAKER:
2867 if (!PhoneUtils.isSpeakerOn(this)) {
2868 // Switch away from Bluetooth, if it was active.
2869 if (isBluetoothAvailable() && isBluetoothAudioConnected()) {
2870 disconnectBluetoothAudio();
2871 }
2872 PhoneUtils.turnOnSpeaker(this, true, true);
2873 }
2874 break;
2875
2876 case BLUETOOTH:
2877 // If already connected to BT, there's nothing to do here.
2878 if (isBluetoothAvailable() && !isBluetoothAudioConnected()) {
2879 // Manually turn the speaker phone off, instead of allowing the
2880 // Bluetooth audio routing to handle it, since there's other
2881 // important state-updating that needs to happen in the
2882 // PhoneUtils.turnOnSpeaker() method.
2883 // (Similarly, whenever the user turns *on* the speaker, we
2884 // manually disconnect the active bluetooth headset;
2885 // see toggleSpeaker() and/or switchInCallAudio().)
2886 if (PhoneUtils.isSpeakerOn(this)) {
2887 PhoneUtils.turnOnSpeaker(this, false, true);
2888 }
2889 connectBluetoothAudio();
2890 }
2891 break;
2892
2893 case EARPIECE:
2894 // Switch to either the handset earpiece, or the wired headset (if connected.)
2895 // (Do this by simply making sure both speaker and bluetooth are off.)
2896 if (isBluetoothAvailable() && isBluetoothAudioConnected()) {
2897 disconnectBluetoothAudio();
2898 }
2899 if (PhoneUtils.isSpeakerOn(this)) {
2900 PhoneUtils.turnOnSpeaker(this, false, true);
2901 }
2902 break;
2903
2904 default:
2905 Log.wtf(LOG_TAG, "switchInCallAudio: unexpected mode " + newMode);
2906 break;
2907 }
2908
2909 // And finally, update the InCallTouchUi widget (since the "audio
2910 // mode" button might need to change its appearance based on the
2911 // new audio state.)
2912 updateInCallTouchUi();
2913 }
2914
2915 /**
2916 * Handle a click on the "Open/Close dialpad" button.
2917 *
2918 * @see DTMFTwelveKeyDialer#openDialer(boolean)
2919 * @see DTMFTwelveKeyDialer#closeDialer(boolean)
2920 */
2921 private void onOpenCloseDialpad() {
2922 if (VDBG) log("onOpenCloseDialpad()...");
2923 if (mDialer.isOpened()) {
2924 closeDialpadInternal(true); // do the "closing" animation
2925 } else {
2926 openDialpadInternal(true); // do the "opening" animation
2927 }
2928 mApp.updateProximitySensorMode(mCM.getState());
2929 }
2930
2931 /** Internal wrapper around {@link DTMFTwelveKeyDialer#openDialer(boolean)} */
2932 private void openDialpadInternal(boolean animate) {
2933 mDialer.openDialer(animate);
2934 // And update the InCallUiState (so that we'll restore the dialpad
2935 // to the correct state if we get paused/resumed).
2936 mApp.inCallUiState.showDialpad = true;
2937 }
2938
2939 // Internal wrapper around DTMFTwelveKeyDialer.closeDialer()
2940 private void closeDialpadInternal(boolean animate) {
2941 mDialer.closeDialer(animate);
2942 // And update the InCallUiState (so that we'll restore the dialpad
2943 // to the correct state if we get paused/resumed).
2944 mApp.inCallUiState.showDialpad = false;
2945 }
2946
2947 /**
2948 * Handles button clicks from the InCallTouchUi widget.
2949 */
2950 /* package */ void handleOnscreenButtonClick(int id) {
2951 if (DBG) log("handleOnscreenButtonClick(id " + id + ")...");
2952
2953 switch (id) {
2954 // Actions while an incoming call is ringing:
2955 case R.id.incomingCallAnswer:
2956 internalAnswerCall();
2957 break;
2958 case R.id.incomingCallReject:
2959 hangupRingingCall();
2960 break;
2961 case R.id.incomingCallRespondViaSms:
2962 internalRespondViaSms();
2963 break;
2964
2965 // The other regular (single-tap) buttons used while in-call:
2966 case R.id.holdButton:
2967 onHoldClick();
2968 break;
2969 case R.id.swapButton:
2970 internalSwapCalls();
2971 break;
2972 case R.id.endButton:
2973 internalHangup();
2974 break;
2975 case R.id.dialpadButton:
2976 onOpenCloseDialpad();
2977 break;
2978 case R.id.muteButton:
2979 onMuteClick();
2980 break;
2981 case R.id.addButton:
2982 PhoneUtils.startNewCall(mCM); // Fires off an ACTION_DIAL intent
2983 break;
2984 case R.id.mergeButton:
2985 case R.id.cdmaMergeButton:
2986 PhoneUtils.mergeCalls(mCM);
2987 break;
2988 case R.id.manageConferenceButton:
2989 // Show the Manage Conference panel.
2990 setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
2991 requestUpdateScreen();
2992 break;
2993
2994 default:
2995 Log.w(LOG_TAG, "handleOnscreenButtonClick: unexpected ID " + id);
2996 break;
2997 }
2998
2999 // Clicking any onscreen UI element counts as explicit "user activity".
3000 mApp.pokeUserActivity();
3001
3002 // Just in case the user clicked a "stateful" UI element (like one
3003 // of the toggle buttons), we force the in-call buttons to update,
3004 // to make sure the user sees the *new* current state.
3005 //
3006 // Note that some in-call buttons will *not* immediately change the
3007 // state of the UI, namely those that send a request to the telephony
3008 // layer (like "Hold" or "End call".) For those buttons, the
3009 // updateInCallTouchUi() call here won't have any visible effect.
3010 // Instead, the UI will be updated eventually when the next
3011 // onPhoneStateChanged() event comes in and triggers an updateScreen()
3012 // call.
3013 //
3014 // TODO: updateInCallTouchUi() is overkill here; it would be
3015 // more efficient to update *only* the affected button(s).
3016 // (But this isn't a big deal since updateInCallTouchUi() is pretty
3017 // cheap anyway...)
3018 updateInCallTouchUi();
3019 }
3020
3021 /**
3022 * Display a status or error indication to the user according to the
3023 * specified InCallUiState.CallStatusCode value.
3024 */
3025 private void showStatusIndication(CallStatusCode status) {
3026 switch (status) {
3027 case SUCCESS:
3028 // The InCallScreen does not need to display any kind of error indication,
3029 // so we shouldn't have gotten here in the first place.
3030 Log.wtf(LOG_TAG, "showStatusIndication: nothing to display");
3031 break;
3032
3033 case POWER_OFF:
3034 // Radio is explictly powered off, presumably because the
3035 // device is in airplane mode.
3036 //
3037 // TODO: For now this UI is ultra-simple: we simply display
3038 // a message telling the user to turn off airplane mode.
3039 // But it might be nicer for the dialog to offer the option
3040 // to turn the radio on right there (and automatically retry
3041 // the call once network registration is complete.)
3042 showGenericErrorDialog(R.string.incall_error_power_off,
3043 true /* isStartupError */);
3044 break;
3045
3046 case EMERGENCY_ONLY:
3047 // Only emergency numbers are allowed, but we tried to dial
3048 // a non-emergency number.
3049 // (This state is currently unused; see comments above.)
3050 showGenericErrorDialog(R.string.incall_error_emergency_only,
3051 true /* isStartupError */);
3052 break;
3053
3054 case OUT_OF_SERVICE:
3055 // No network connection.
3056 showGenericErrorDialog(R.string.incall_error_out_of_service,
3057 true /* isStartupError */);
3058 break;
3059
3060 case NO_PHONE_NUMBER_SUPPLIED:
3061 // The supplied Intent didn't contain a valid phone number.
3062 // (This is rare and should only ever happen with broken
3063 // 3rd-party apps.) For now just show a generic error.
3064 showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied,
3065 true /* isStartupError */);
3066 break;
3067
3068 case DIALED_MMI:
3069 // Our initial phone number was actually an MMI sequence.
3070 // There's no real "error" here, but we do bring up the
3071 // a Toast (as requested of the New UI paradigm).
3072 //
3073 // In-call MMIs do not trigger the normal MMI Initiate
3074 // Notifications, so we should notify the user here.
3075 // Otherwise, the code in PhoneUtils.java should handle
3076 // user notifications in the form of Toasts or Dialogs.
3077 if (mCM.getState() == PhoneConstants.State.OFFHOOK) {
3078 Toast.makeText(mApp, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
3079 .show();
3080 }
3081 break;
3082
3083 case CALL_FAILED:
3084 // We couldn't successfully place the call; there was some
3085 // failure in the telephony layer.
3086 // TODO: Need UI spec for this failure case; for now just
3087 // show a generic error.
3088 showGenericErrorDialog(R.string.incall_error_call_failed,
3089 true /* isStartupError */);
3090 break;
3091
3092 case VOICEMAIL_NUMBER_MISSING:
3093 // We tried to call a voicemail: URI but the device has no
3094 // voicemail number configured.
3095 handleMissingVoiceMailNumber();
3096 break;
3097
3098 case CDMA_CALL_LOST:
3099 // This status indicates that InCallScreen should display the
3100 // CDMA-specific "call lost" dialog. (If an outgoing call fails,
3101 // and the CDMA "auto-retry" feature is enabled, *and* the retried
3102 // call fails too, we display this specific dialog.)
3103 //
3104 // TODO: currently unused; see InCallUiState.needToShowCallLostDialog
3105 break;
3106
3107 case EXITED_ECM:
3108 // This status indicates that InCallScreen needs to display a
3109 // warning that we're exiting ECM (emergency callback mode).
3110 showExitingECMDialog();
3111 break;
3112
3113 default:
3114 throw new IllegalStateException(
3115 "showStatusIndication: unexpected status code: " + status);
3116 }
3117
3118 // TODO: still need to make sure that pressing OK or BACK from
3119 // *any* of the dialogs we launch here ends up calling
3120 // inCallUiState.clearPendingCallStatusCode()
3121 // *and*
3122 // make sure the Dialog handles both OK *and* cancel by calling
3123 // endInCallScreenSession. (See showGenericErrorDialog() for an
3124 // example.)
3125 //
3126 // (showGenericErrorDialog() currently does this correctly,
3127 // but handleMissingVoiceMailNumber() probably needs to be fixed too.)
3128 //
3129 // Also need to make sure that bailing out of any of these dialogs by
3130 // pressing Home clears out the pending status code too. (If you do
3131 // that, neither the dialog's clickListener *or* cancelListener seems
3132 // to run...)
3133 }
3134
3135 /**
3136 * Utility function to bring up a generic "error" dialog, and then bail
3137 * out of the in-call UI when the user hits OK (or the BACK button.)
3138 */
3139 private void showGenericErrorDialog(int resid, boolean isStartupError) {
3140 CharSequence msg = getResources().getText(resid);
3141 if (DBG) log("showGenericErrorDialog('" + msg + "')...");
3142
3143 // create the clicklistener and cancel listener as needed.
3144 DialogInterface.OnClickListener clickListener;
3145 OnCancelListener cancelListener;
3146 if (isStartupError) {
3147 clickListener = new DialogInterface.OnClickListener() {
3148 public void onClick(DialogInterface dialog, int which) {
3149 bailOutAfterErrorDialog();
3150 }};
3151 cancelListener = new OnCancelListener() {
3152 public void onCancel(DialogInterface dialog) {
3153 bailOutAfterErrorDialog();
3154 }};
3155 } else {
3156 clickListener = new DialogInterface.OnClickListener() {
3157 public void onClick(DialogInterface dialog, int which) {
3158 delayedCleanupAfterDisconnect();
3159 }};
3160 cancelListener = new OnCancelListener() {
3161 public void onCancel(DialogInterface dialog) {
3162 delayedCleanupAfterDisconnect();
3163 }};
3164 }
3165
3166 // TODO: Consider adding a setTitle() call here (with some generic
3167 // "failure" title?)
3168 mGenericErrorDialog = new AlertDialog.Builder(this)
3169 .setMessage(msg)
3170 .setPositiveButton(R.string.ok, clickListener)
3171 .setOnCancelListener(cancelListener)
3172 .create();
3173
3174 // When the dialog is up, completely hide the in-call UI
3175 // underneath (which is in a partially-constructed state).
3176 mGenericErrorDialog.getWindow().addFlags(
3177 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
3178
3179 mGenericErrorDialog.show();
3180 }
3181
3182 private void showCallLostDialog() {
3183 if (DBG) log("showCallLostDialog()...");
3184
3185 // Don't need to show the dialog if InCallScreen isn't in the forgeround
3186 if (!mIsForegroundActivity) {
3187 if (DBG) log("showCallLostDialog: not the foreground Activity! Bailing out...");
3188 return;
3189 }
3190
3191 // Don't need to show the dialog again, if there is one already.
3192 if (mCallLostDialog != null) {
3193 if (DBG) log("showCallLostDialog: There is a mCallLostDialog already.");
3194 return;
3195 }
3196
3197 mCallLostDialog = new AlertDialog.Builder(this)
3198 .setMessage(R.string.call_lost)
3199 .setIconAttribute(android.R.attr.alertDialogIcon)
3200 .create();
3201 mCallLostDialog.show();
3202 }
3203
3204 /**
3205 * Displays the "Exiting ECM" warning dialog.
3206 *
3207 * Background: If the phone is currently in ECM (Emergency callback
3208 * mode) and we dial a non-emergency number, that automatically
3209 * *cancels* ECM. (That behavior comes from CdmaCallTracker.dial().)
3210 * When that happens, we need to warn the user that they're no longer
3211 * in ECM (bug 4207607.)
3212 *
3213 * So bring up a dialog explaining what's happening. There's nothing
3214 * for the user to do, by the way; we're simply providing an
3215 * indication that they're exiting ECM. We *could* use a Toast for
3216 * this, but toasts are pretty easy to miss, so instead use a dialog
3217 * with a single "OK" button.
3218 *
3219 * TODO: it's ugly that the code here has to make assumptions about
3220 * the behavior of the telephony layer (namely that dialing a
3221 * non-emergency number while in ECM causes us to exit ECM.)
3222 *
3223 * Instead, this warning dialog should really be triggered by our
3224 * handler for the
3225 * TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED intent in
3226 * PhoneApp.java. But that won't work until that intent also
3227 * includes a *reason* why we're exiting ECM, since we need to
3228 * display this dialog when exiting ECM because of an outgoing call,
3229 * but NOT if we're exiting ECM because the user manually turned it
3230 * off via the EmergencyCallbackModeExitDialog.
3231 *
3232 * Or, it might be simpler to just have outgoing non-emergency calls
3233 * *not* cancel ECM. That way the UI wouldn't have to do anything
3234 * special here.
3235 */
3236 private void showExitingECMDialog() {
3237 Log.i(LOG_TAG, "showExitingECMDialog()...");
3238
3239 if (mExitingECMDialog != null) {
3240 if (DBG) log("- DISMISSING mExitingECMDialog.");
3241 mExitingECMDialog.dismiss(); // safe even if already dismissed
3242 mExitingECMDialog = null;
3243 }
3244
3245 // When the user dismisses the "Exiting ECM" dialog, we clear out
3246 // the pending call status code field (since we're done with this
3247 // dialog), but do *not* bail out of the InCallScreen.
3248
3249 final InCallUiState inCallUiState = mApp.inCallUiState;
3250 DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
3251 public void onClick(DialogInterface dialog, int which) {
3252 inCallUiState.clearPendingCallStatusCode();
3253 }};
3254 OnCancelListener cancelListener = new OnCancelListener() {
3255 public void onCancel(DialogInterface dialog) {
3256 inCallUiState.clearPendingCallStatusCode();
3257 }};
3258
3259 // Ultra-simple AlertDialog with only an OK button:
3260 mExitingECMDialog = new AlertDialog.Builder(this)
3261 .setMessage(R.string.progress_dialog_exiting_ecm)
3262 .setPositiveButton(R.string.ok, clickListener)
3263 .setOnCancelListener(cancelListener)
3264 .create();
3265 mExitingECMDialog.getWindow().addFlags(
3266 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
3267 mExitingECMDialog.show();
3268 }
3269
3270 private void bailOutAfterErrorDialog() {
3271 if (mGenericErrorDialog != null) {
3272 if (DBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
3273 mGenericErrorDialog.dismiss();
3274 mGenericErrorDialog = null;
3275 }
3276 if (DBG) log("bailOutAfterErrorDialog(): end InCallScreen session...");
3277
3278 // Now that the user has dismissed the error dialog (presumably by
3279 // either hitting the OK button or pressing Back, we can now reset
3280 // the pending call status code field.
3281 //
3282 // (Note that the pending call status is NOT cleared simply
3283 // by the InCallScreen being paused or finished, since the resulting
3284 // dialog is supposed to persist across orientation changes or if the
3285 // screen turns off.)
3286 //
3287 // See the "Error / diagnostic indications" section of
3288 // InCallUiState.java for more detailed info about the
3289 // pending call status code field.
3290 final InCallUiState inCallUiState = mApp.inCallUiState;
3291 inCallUiState.clearPendingCallStatusCode();
3292
3293 // Force the InCallScreen to truly finish(), rather than just
3294 // moving it to the back of the activity stack (which is what
3295 // our finish() method usually does.)
3296 // This is necessary to avoid an obscure scenario where the
3297 // InCallScreen can get stuck in an inconsistent state, somehow
3298 // causing a *subsequent* outgoing call to fail (bug 4172599).
3299 endInCallScreenSession(true /* force a real finish() call */);
3300 }
3301
3302 /**
3303 * Dismisses (and nulls out) all persistent Dialogs managed
3304 * by the InCallScreen. Useful if (a) we're about to bring up
3305 * a dialog and want to pre-empt any currently visible dialogs,
3306 * or (b) as a cleanup step when the Activity is going away.
3307 */
3308 private void dismissAllDialogs() {
3309 if (DBG) log("dismissAllDialogs()...");
3310
3311 // Note it's safe to dismiss() a dialog that's already dismissed.
3312 // (Even if the AlertDialog object(s) below are still around, it's
3313 // possible that the actual dialog(s) may have already been
3314 // dismissed by the user.)
3315
3316 if (mMissingVoicemailDialog != null) {
3317 if (VDBG) log("- DISMISSING mMissingVoicemailDialog.");
3318 mMissingVoicemailDialog.dismiss();
3319 mMissingVoicemailDialog = null;
3320 }
3321 if (mMmiStartedDialog != null) {
3322 if (VDBG) log("- DISMISSING mMmiStartedDialog.");
3323 mMmiStartedDialog.dismiss();
3324 mMmiStartedDialog = null;
3325 }
3326 if (mGenericErrorDialog != null) {
3327 if (VDBG) log("- DISMISSING mGenericErrorDialog.");
3328 mGenericErrorDialog.dismiss();
3329 mGenericErrorDialog = null;
3330 }
3331 if (mSuppServiceFailureDialog != null) {
3332 if (VDBG) log("- DISMISSING mSuppServiceFailureDialog.");
3333 mSuppServiceFailureDialog.dismiss();
3334 mSuppServiceFailureDialog = null;
3335 }
3336 if (mWaitPromptDialog != null) {
3337 if (VDBG) log("- DISMISSING mWaitPromptDialog.");
3338 mWaitPromptDialog.dismiss();
3339 mWaitPromptDialog = null;
3340 }
3341 if (mWildPromptDialog != null) {
3342 if (VDBG) log("- DISMISSING mWildPromptDialog.");
3343 mWildPromptDialog.dismiss();
3344 mWildPromptDialog = null;
3345 }
3346 if (mCallLostDialog != null) {
3347 if (VDBG) log("- DISMISSING mCallLostDialog.");
3348 mCallLostDialog.dismiss();
3349 mCallLostDialog = null;
3350 }
3351 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
3352 || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
3353 && mApp.otaUtils != null) {
3354 mApp.otaUtils.dismissAllOtaDialogs();
3355 }
3356 if (mPausePromptDialog != null) {
3357 if (DBG) log("- DISMISSING mPausePromptDialog.");
3358 mPausePromptDialog.dismiss();
3359 mPausePromptDialog = null;
3360 }
3361 if (mExitingECMDialog != null) {
3362 if (DBG) log("- DISMISSING mExitingECMDialog.");
3363 mExitingECMDialog.dismiss();
3364 mExitingECMDialog = null;
3365 }
3366 }
3367
3368 /**
3369 * Updates the state of the onscreen "progress indication" used in
3370 * some (relatively rare) scenarios where we need to wait for
3371 * something to happen before enabling the in-call UI.
3372 *
3373 * If necessary, this method will cause a ProgressDialog (i.e. a
3374 * spinning wait cursor) to be drawn *on top of* whatever the current
3375 * state of the in-call UI is.
3376 *
3377 * @see InCallUiState.ProgressIndicationType
3378 */
3379 private void updateProgressIndication() {
3380 // If an incoming call is ringing, that takes priority over any
3381 // possible value of inCallUiState.progressIndication.
3382 if (mCM.hasActiveRingingCall()) {
3383 dismissProgressIndication();
3384 return;
3385 }
3386
3387 // Otherwise, put up a progress indication if indicated by the
3388 // inCallUiState.progressIndication field.
3389 final InCallUiState inCallUiState = mApp.inCallUiState;
3390 switch (inCallUiState.getProgressIndication()) {
3391 case NONE:
3392 // No progress indication necessary, so make sure it's dismissed.
3393 dismissProgressIndication();
3394 break;
3395
3396 case TURNING_ON_RADIO:
3397 showProgressIndication(
3398 R.string.emergency_enable_radio_dialog_title,
3399 R.string.emergency_enable_radio_dialog_message);
3400 break;
3401
3402 case RETRYING:
3403 showProgressIndication(
3404 R.string.emergency_enable_radio_dialog_title,
3405 R.string.emergency_enable_radio_dialog_retry);
3406 break;
3407
3408 default:
3409 Log.wtf(LOG_TAG, "updateProgressIndication: unexpected value: "
3410 + inCallUiState.getProgressIndication());
3411 dismissProgressIndication();
3412 break;
3413 }
3414 }
3415
3416 /**
3417 * Show an onscreen "progress indication" with the specified title and message.
3418 */
3419 private void showProgressIndication(int titleResId, int messageResId) {
3420 if (DBG) log("showProgressIndication(message " + messageResId + ")...");
3421
3422 // TODO: make this be a no-op if the progress indication is
3423 // already visible with the exact same title and message.
3424
3425 dismissProgressIndication(); // Clean up any prior progress indication
3426 mProgressDialog = new ProgressDialog(this);
3427 mProgressDialog.setTitle(getText(titleResId));
3428 mProgressDialog.setMessage(getText(messageResId));
3429 mProgressDialog.setIndeterminate(true);
3430 mProgressDialog.setCancelable(false);
3431 mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
3432 mProgressDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
3433 mProgressDialog.show();
3434 }
3435
3436 /**
3437 * Dismiss the onscreen "progress indication" (if present).
3438 */
3439 private void dismissProgressIndication() {
3440 if (DBG) log("dismissProgressIndication()...");
3441 if (mProgressDialog != null) {
3442 mProgressDialog.dismiss(); // safe even if already dismissed
3443 mProgressDialog = null;
3444 }
3445 }
3446
3447
3448 //
3449 // Helper functions for answering incoming calls.
3450 //
3451
3452 /**
3453 * Answer a ringing call. This method does nothing if there's no
3454 * ringing or waiting call.
3455 */
3456 private void internalAnswerCall() {
3457 if (DBG) log("internalAnswerCall()...");
3458 // if (DBG) PhoneUtils.dumpCallState(mPhone);
3459
3460 final boolean hasRingingCall = mCM.hasActiveRingingCall();
3461
3462 if (hasRingingCall) {
3463 Phone phone = mCM.getRingingPhone();
3464 Call ringing = mCM.getFirstActiveRingingCall();
3465 int phoneType = phone.getPhoneType();
3466 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
3467 if (DBG) log("internalAnswerCall: answering (CDMA)...");
3468 if (mCM.hasActiveFgCall()
3469 && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
3470 // The incoming call is CDMA call and the ongoing
3471 // call is a SIP call. The CDMA network does not
3472 // support holding an active call, so there's no
3473 // way to swap between a CDMA call and a SIP call.
3474 // So for now, we just don't allow a CDMA call and
3475 // a SIP call to be active at the same time.We'll
3476 // "answer incoming, end ongoing" in this case.
3477 if (DBG) log("internalAnswerCall: answer "
3478 + "CDMA incoming and end SIP ongoing");
3479 PhoneUtils.answerAndEndActive(mCM, ringing);
3480 } else {
3481 PhoneUtils.answerCall(ringing);
3482 }
3483 } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
3484 if (DBG) log("internalAnswerCall: answering (SIP)...");
3485 if (mCM.hasActiveFgCall()
3486 && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
3487 // Similar to the PHONE_TYPE_CDMA handling.
3488 // The incoming call is SIP call and the ongoing
3489 // call is a CDMA call. The CDMA network does not
3490 // support holding an active call, so there's no
3491 // way to swap between a CDMA call and a SIP call.
3492 // So for now, we just don't allow a CDMA call and
3493 // a SIP call to be active at the same time.We'll
3494 // "answer incoming, end ongoing" in this case.
3495 if (DBG) log("internalAnswerCall: answer "
3496 + "SIP incoming and end CDMA ongoing");
3497 PhoneUtils.answerAndEndActive(mCM, ringing);
3498 } else {
3499 PhoneUtils.answerCall(ringing);
3500 }
3501 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
3502 if (DBG) log("internalAnswerCall: answering (GSM)...");
3503 // GSM: this is usually just a wrapper around
3504 // PhoneUtils.answerCall(), *but* we also need to do
3505 // something special for the "both lines in use" case.
3506
3507 final boolean hasActiveCall = mCM.hasActiveFgCall();
3508 final boolean hasHoldingCall = mCM.hasActiveBgCall();
3509
3510 if (hasActiveCall && hasHoldingCall) {
3511 if (DBG) log("internalAnswerCall: answering (both lines in use!)...");
3512 // The relatively rare case where both lines are
3513 // already in use. We "answer incoming, end ongoing"
3514 // in this case, according to the current UI spec.
3515 PhoneUtils.answerAndEndActive(mCM, ringing);
3516
3517 // Alternatively, we could use
3518 // PhoneUtils.answerAndEndHolding(mPhone);
3519 // here to end the on-hold call instead.
3520 } else {
3521 if (DBG) log("internalAnswerCall: answering...");
3522 PhoneUtils.answerCall(ringing); // Automatically holds the current active call,
3523 // if there is one
3524 }
3525 } else {
3526 throw new IllegalStateException("Unexpected phone type: " + phoneType);
3527 }
3528
3529 // Call origin is valid only with outgoing calls. Disable it on incoming calls.
3530 mApp.setLatestActiveCallOrigin(null);
3531 }
3532 }
3533
3534 /**
3535 * Hang up the ringing call (aka "Don't answer").
3536 */
3537 /* package */ void hangupRingingCall() {
3538 if (DBG) log("hangupRingingCall()...");
3539 if (VDBG) PhoneUtils.dumpCallManager();
3540 // In the rare case when multiple calls are ringing, the UI policy
3541 // it to always act on the first ringing call.
3542 PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
3543 }
3544
3545 /**
3546 * Silence the ringer (if an incoming call is ringing.)
3547 */
3548 private void internalSilenceRinger() {
3549 if (DBG) log("internalSilenceRinger()...");
3550 final CallNotifier notifier = mApp.notifier;
3551 if (notifier.isRinging()) {
3552 // ringer is actually playing, so silence it.
3553 notifier.silenceRinger();
3554 }
3555 }
3556
3557 /**
3558 * Respond via SMS to the ringing call.
3559 * @see RespondViaSmsManager
3560 */
3561 private void internalRespondViaSms() {
3562 log("internalRespondViaSms()...");
3563 if (VDBG) PhoneUtils.dumpCallManager();
3564
3565 // In the rare case when multiple calls are ringing, the UI policy
3566 // it to always act on the first ringing call.
3567 Call ringingCall = mCM.getFirstActiveRingingCall();
3568
3569 mRespondViaSmsManager.showRespondViaSmsPopup(ringingCall);
3570
3571 // Silence the ringer, since it would be distracting while you're trying
3572 // to pick a response. (Note that we'll restart the ringer if you bail
3573 // out of the popup, though; see RespondViaSmsCancelListener.)
3574 internalSilenceRinger();
3575 }
3576
3577 /**
3578 * Hang up the current active call.
3579 */
3580 private void internalHangup() {
3581 PhoneConstants.State state = mCM.getState();
3582 log("internalHangup()... phone state = " + state);
3583
3584 // Regardless of the phone state, issue a hangup request.
3585 // (If the phone is already idle, this call will presumably have no
3586 // effect (but also see the note below.))
3587 PhoneUtils.hangup(mCM);
3588
3589 // If the user just hung up the only active call, we'll eventually exit
3590 // the in-call UI after the following sequence:
3591 // - When the hangup() succeeds, we'll get a DISCONNECT event from
3592 // the telephony layer (see onDisconnect()).
3593 // - We immediately switch to the "Call ended" state (see the "delayed
3594 // bailout" code path in onDisconnect()) and also post a delayed
3595 // DELAYED_CLEANUP_AFTER_DISCONNECT message.
3596 // - When the DELAYED_CLEANUP_AFTER_DISCONNECT message comes in (see
3597 // delayedCleanupAfterDisconnect()) we do some final cleanup, and exit
3598 // this activity unless the phone is still in use (i.e. if there's
3599 // another call, or something else going on like an active MMI
3600 // sequence.)
3601
3602 if (state == PhoneConstants.State.IDLE) {
3603 // The user asked us to hang up, but the phone was (already) idle!
3604 Log.w(LOG_TAG, "internalHangup(): phone is already IDLE!");
3605
3606 // This is rare, but can happen in a few cases:
3607 // (a) If the user quickly double-taps the "End" button. In this case
3608 // we'll see that 2nd press event during the brief "Call ended"
3609 // state (where the phone is IDLE), or possibly even before the
3610 // radio has been able to respond to the initial hangup request.
3611 // (b) More rarely, this can happen if the user presses "End" at the
3612 // exact moment that the call ends on its own (like because of the
3613 // other person hanging up.)
3614 // (c) Finally, this could also happen if we somehow get stuck here on
3615 // the InCallScreen with the phone truly idle, perhaps due to a
3616 // bug where we somehow *didn't* exit when the phone became idle
3617 // in the first place.
3618
3619 // TODO: as a "safety valve" for case (c), consider immediately
3620 // bailing out of the in-call UI right here. (The user can always
3621 // bail out by pressing Home, of course, but they'll probably try
3622 // pressing End first.)
3623 //
3624 // Log.i(LOG_TAG, "internalHangup(): phone is already IDLE! Bailing out...");
3625 // endInCallScreenSession();
3626 }
3627 }
3628
3629 /**
3630 * InCallScreen-specific wrapper around PhoneUtils.switchHoldingAndActive().
3631 */
3632 private void internalSwapCalls() {
3633 if (DBG) log("internalSwapCalls()...");
3634
3635 // Any time we swap calls, force the DTMF dialpad to close.
3636 // (We want the regular in-call UI to be visible right now, so the
3637 // user can clearly see which call is now in the foreground.)
3638 closeDialpadInternal(true); // do the "closing" animation
3639
3640 // Also, clear out the "history" of DTMF digits you typed, to make
3641 // sure you don't see digits from call #1 while call #2 is active.
3642 // (Yes, this does mean that swapping calls twice will cause you
3643 // to lose any previous digits from the current call; see the TODO
3644 // comment on DTMFTwelvKeyDialer.clearDigits() for more info.)
3645 mDialer.clearDigits();
3646
3647 // Swap the fg and bg calls.
3648 // In the future we may provides some way for user to choose among
3649 // multiple background calls, for now, always act on the first background calll.
3650 PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
3651
3652 // If we have a valid BluetoothPhoneService then since CDMA network or
3653 // Telephony FW does not send us information on which caller got swapped
3654 // we need to update the second call active state in BluetoothPhoneService internally
3655 if (mCM.getBgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
3656 IBluetoothHeadsetPhone btPhone = mApp.getBluetoothPhoneService();
3657 if (btPhone != null) {
3658 try {
3659 btPhone.cdmaSwapSecondCallState();
3660 } catch (RemoteException e) {
3661 Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
3662 }
3663 }
3664 }
3665
3666 }
3667
3668 /**
3669 * Sets the current high-level "mode" of the in-call UI.
3670 *
3671 * NOTE: if newMode is CALL_ENDED, the caller is responsible for
3672 * posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
3673 * sure the "call ended" state goes away after a couple of seconds.
3674 *
3675 * Note this method does NOT refresh of the onscreen UI; the caller is
3676 * responsible for calling updateScreen() or requestUpdateScreen() if
3677 * necessary.
3678 */
3679 private void setInCallScreenMode(InCallScreenMode newMode) {
3680 if (DBG) log("setInCallScreenMode: " + newMode);
3681 mApp.inCallUiState.inCallScreenMode = newMode;
3682
3683 switch (newMode) {
3684 case MANAGE_CONFERENCE:
3685 if (!PhoneUtils.isConferenceCall(mCM.getActiveFgCall())) {
3686 Log.w(LOG_TAG, "MANAGE_CONFERENCE: no active conference call!");
3687 // Hide the Manage Conference panel, return to NORMAL mode.
3688 setInCallScreenMode(InCallScreenMode.NORMAL);
3689 return;
3690 }
3691 List<Connection> connections = mCM.getFgCallConnections();
3692 // There almost certainly will be > 1 connection,
3693 // since isConferenceCall() just returned true.
3694 if ((connections == null) || (connections.size() <= 1)) {
3695 Log.w(LOG_TAG,
3696 "MANAGE_CONFERENCE: Bogus TRUE from isConferenceCall(); connections = "
3697 + connections);
3698 // Hide the Manage Conference panel, return to NORMAL mode.
3699 setInCallScreenMode(InCallScreenMode.NORMAL);
3700 return;
3701 }
3702
3703 // TODO: Don't do this here. The call to
3704 // initManageConferencePanel() should instead happen
3705 // automagically in ManageConferenceUtils the very first
3706 // time you call updateManageConferencePanel() or
3707 // setPanelVisible(true).
3708 mManageConferenceUtils.initManageConferencePanel(); // if necessary
3709
3710 mManageConferenceUtils.updateManageConferencePanel(connections);
3711
3712 // The "Manage conference" UI takes up the full main frame,
3713 // replacing the CallCard PopupWindow.
3714 mManageConferenceUtils.setPanelVisible(true);
3715
3716 // Start the chronometer.
3717 // TODO: Similarly, we shouldn't expose startConferenceTime()
3718 // and stopConferenceTime(); the ManageConferenceUtils
3719 // class ought to manage the conferenceTime widget itself
3720 // based on setPanelVisible() calls.
3721
3722 // Note: there is active Fg call since we are in conference call
3723 long callDuration =
3724 mCM.getActiveFgCall().getEarliestConnection().getDurationMillis();
3725 mManageConferenceUtils.startConferenceTime(
3726 SystemClock.elapsedRealtime() - callDuration);
3727
3728 // No need to close the dialer here, since the Manage
3729 // Conference UI will just cover it up anyway.
3730
3731 break;
3732
3733 case CALL_ENDED:
3734 case NORMAL:
3735 mManageConferenceUtils.setPanelVisible(false);
3736 mManageConferenceUtils.stopConferenceTime();
3737 break;
3738
3739 case OTA_NORMAL:
3740 mApp.otaUtils.setCdmaOtaInCallScreenUiState(
3741 OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL);
3742 break;
3743
3744 case OTA_ENDED:
3745 mApp.otaUtils.setCdmaOtaInCallScreenUiState(
3746 OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
3747 break;
3748
3749 case UNDEFINED:
3750 // Set our Activities intent to ACTION_UNDEFINED so
3751 // that if we get resumed after we've completed a call
3752 // the next call will not cause checkIsOtaCall to
3753 // return true.
3754 //
3755 // TODO(OTASP): update these comments
3756 //
3757 // With the framework as of October 2009 the sequence below
3758 // causes the framework to call onResume, onPause, onNewIntent,
3759 // onResume. If we don't call setIntent below then when the
3760 // first onResume calls checkIsOtaCall via checkOtaspStateOnResume it will
3761 // return true and the Activity will be confused.
3762 //
3763 // 1) Power up Phone A
3764 // 2) Place *22899 call and activate Phone A
3765 // 3) Press the power key on Phone A to turn off the display
3766 // 4) Call Phone A from Phone B answering Phone A
3767 // 5) The screen will be blank (Should be normal InCallScreen)
3768 // 6) Hang up the Phone B
3769 // 7) Phone A displays the activation screen.
3770 //
3771 // Step 3 is the critical step to cause the onResume, onPause
3772 // onNewIntent, onResume sequence. If step 3 is skipped the
3773 // sequence will be onNewIntent, onResume and all will be well.
3774 setIntent(new Intent(ACTION_UNDEFINED));
3775
3776 // Cleanup Ota Screen if necessary and set the panel
3777 // to VISIBLE.
3778 if (mCM.getState() != PhoneConstants.State.OFFHOOK) {
3779 if (mApp.otaUtils != null) {
3780 mApp.otaUtils.cleanOtaScreen(true);
3781 }
3782 } else {
3783 log("WARNING: Setting mode to UNDEFINED but phone is OFFHOOK,"
3784 + " skip cleanOtaScreen.");
3785 }
3786 break;
3787 }
3788 }
3789
3790 /**
3791 * @return true if the "Manage conference" UI is currently visible.
3792 */
3793 /* package */ boolean isManageConferenceMode() {
3794 return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE);
3795 }
3796
3797 /**
3798 * Checks if the "Manage conference" UI needs to be updated.
3799 * If the state of the current conference call has changed
3800 * since our previous call to updateManageConferencePanel()),
3801 * do a fresh update. Also, if the current call is no longer a
3802 * conference call at all, bail out of the "Manage conference" UI and
3803 * return to InCallScreenMode.NORMAL mode.
3804 */
3805 private void updateManageConferencePanelIfNecessary() {
3806 if (VDBG) log("updateManageConferencePanelIfNecessary: " + mCM.getActiveFgCall() + "...");
3807
3808 List<Connection> connections = mCM.getFgCallConnections();
3809 if (connections == null) {
3810 if (VDBG) log("==> no connections on foreground call!");
3811 // Hide the Manage Conference panel, return to NORMAL mode.
3812 setInCallScreenMode(InCallScreenMode.NORMAL);
3813 SyncWithPhoneStateStatus status = syncWithPhoneState();
3814 if (status != SyncWithPhoneStateStatus.SUCCESS) {
3815 Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3816 // We shouldn't even be in the in-call UI in the first
3817 // place, so bail out:
3818 if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 1");
3819 endInCallScreenSession();
3820 return;
3821 }
3822 return;
3823 }
3824
3825 int numConnections = connections.size();
3826 if (numConnections <= 1) {
3827 if (VDBG) log("==> foreground call no longer a conference!");
3828 // Hide the Manage Conference panel, return to NORMAL mode.
3829 setInCallScreenMode(InCallScreenMode.NORMAL);
3830 SyncWithPhoneStateStatus status = syncWithPhoneState();
3831 if (status != SyncWithPhoneStateStatus.SUCCESS) {
3832 Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
3833 // We shouldn't even be in the in-call UI in the first
3834 // place, so bail out:
3835 if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 2");
3836 endInCallScreenSession();
3837 return;
3838 }
3839 return;
3840 }
3841
3842 // TODO: the test to see if numConnections has changed can go in
3843 // updateManageConferencePanel(), rather than here.
3844 if (numConnections != mManageConferenceUtils.getNumCallersInConference()) {
3845 if (VDBG) log("==> Conference size has changed; need to rebuild UI!");
3846 mManageConferenceUtils.updateManageConferencePanel(connections);
3847 }
3848 }
3849
3850 /**
3851 * Updates {@link #mCallCard}'s visibility state per DTMF dialpad visibility. They
3852 * cannot be shown simultaneously and thus we should reflect DTMF dialpad visibility into
3853 * another.
3854 *
3855 * Note: During OTA calls or users' managing conference calls, we should *not* call this method
3856 * but manually manage both visibility.
3857 *
3858 * @see #updateScreen()
3859 */
3860 private void updateCallCardVisibilityPerDialerState(boolean animate) {
3861 // We need to hide the CallCard while the dialpad is visible.
3862 if (isDialerOpened()) {
3863 if (VDBG) {
3864 log("- updateCallCardVisibilityPerDialerState(animate="
3865 + animate + "): dialpad open, hide mCallCard...");
3866 }
3867 if (animate) {
3868 AnimationUtils.Fade.hide(mCallCard, View.GONE);
3869 } else {
3870 mCallCard.setVisibility(View.GONE);
3871 }
3872 } else {
3873 // Dialpad is dismissed; bring back the CallCard if it's supposed to be visible.
3874 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.NORMAL)
3875 || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.CALL_ENDED)) {
3876 if (VDBG) {
3877 log("- updateCallCardVisibilityPerDialerState(animate="
3878 + animate + "): dialpad dismissed, show mCallCard...");
3879 }
3880 if (animate) {
3881 AnimationUtils.Fade.show(mCallCard);
3882 } else {
3883 mCallCard.setVisibility(View.VISIBLE);
3884 }
3885 }
3886 }
3887 }
3888
3889 /**
3890 * @see DTMFTwelveKeyDialer#isOpened()
3891 */
3892 /* package */ boolean isDialerOpened() {
3893 return (mDialer != null && mDialer.isOpened());
3894 }
3895
3896 /**
3897 * Called any time the DTMF dialpad is opened.
3898 * @see DTMFTwelveKeyDialer#openDialer(boolean)
3899 */
3900 /* package */ void onDialerOpen(boolean animate) {
3901 if (DBG) log("onDialerOpen()...");
3902
3903 // Update the in-call touch UI.
3904 updateInCallTouchUi();
3905
3906 // Update CallCard UI, which depends on the dialpad.
3907 updateCallCardVisibilityPerDialerState(animate);
3908
3909 // This counts as explicit "user activity".
3910 mApp.pokeUserActivity();
3911
3912 //If on OTA Call, hide OTA Screen
3913 // TODO: This may not be necessary, now that the dialpad is
3914 // always visible in OTA mode.
3915 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
3916 || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
3917 && mApp.otaUtils != null) {
3918 mApp.otaUtils.hideOtaScreen();
3919 }
3920 }
3921
3922 /**
3923 * Called any time the DTMF dialpad is closed.
3924 * @see DTMFTwelveKeyDialer#closeDialer(boolean)
3925 */
3926 /* package */ void onDialerClose(boolean animate) {
3927 if (DBG) log("onDialerClose()...");
3928
3929 // OTA-specific cleanup upon closing the dialpad.
3930 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
3931 || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
3932 || ((mApp.cdmaOtaScreenState != null)
3933 && (mApp.cdmaOtaScreenState.otaScreenState ==
3934 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
3935 if (mApp.otaUtils != null) {
3936 mApp.otaUtils.otaShowProperScreen();
3937 }
3938 }
3939
3940 // Update the in-call touch UI.
3941 updateInCallTouchUi();
3942
3943 // Update CallCard UI, which depends on the dialpad.
3944 updateCallCardVisibilityPerDialerState(animate);
3945
3946 // This counts as explicit "user activity".
3947 mApp.pokeUserActivity();
3948 }
3949
3950 /**
3951 * Determines when we can dial DTMF tones.
3952 */
3953 /* package */ boolean okToDialDTMFTones() {
3954 final boolean hasRingingCall = mCM.hasActiveRingingCall();
3955 final Call.State fgCallState = mCM.getActiveFgCallState();
3956
3957 // We're allowed to send DTMF tones when there's an ACTIVE
3958 // foreground call, and not when an incoming call is ringing
3959 // (since DTMF tones are useless in that state), or if the
3960 // Manage Conference UI is visible (since the tab interferes
3961 // with the "Back to call" button.)
3962
3963 // We can also dial while in ALERTING state because there are
3964 // some connections that never update to an ACTIVE state (no
3965 // indication from the network).
3966 boolean canDial =
3967 (fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
3968 && !hasRingingCall
3969 && (mApp.inCallUiState.inCallScreenMode != InCallScreenMode.MANAGE_CONFERENCE);
3970
3971 if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
3972 ", ringing state: " + hasRingingCall +
3973 ", call screen mode: " + mApp.inCallUiState.inCallScreenMode +
3974 ", result: " + canDial);
3975
3976 return canDial;
3977 }
3978
3979 /**
3980 * @return true if the in-call DTMF dialpad should be available to the
3981 * user, given the current state of the phone and the in-call UI.
3982 * (This is used to control the enabledness of the "Show
3983 * dialpad" onscreen button; see InCallControlState.dialpadEnabled.)
3984 */
3985 /* package */ boolean okToShowDialpad() {
3986 // Very similar to okToDialDTMFTones(), but allow DIALING here.
3987 final Call.State fgCallState = mCM.getActiveFgCallState();
3988 return okToDialDTMFTones() || (fgCallState == Call.State.DIALING);
3989 }
3990
3991 /**
3992 * Initializes the in-call touch UI on devices that need it.
3993 */
3994 private void initInCallTouchUi() {
3995 if (DBG) log("initInCallTouchUi()...");
3996 // TODO: we currently use the InCallTouchUi widget in at least
3997 // some states on ALL platforms. But if some devices ultimately
3998 // end up not using *any* onscreen touch UI, we should make sure
3999 // to not even inflate the InCallTouchUi widget on those devices.
4000 mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
4001 mInCallTouchUi.setInCallScreenInstance(this);
4002
4003 // RespondViaSmsManager implements the "Respond via SMS"
4004 // feature that's triggered from the incoming call widget.
4005 mRespondViaSmsManager = new RespondViaSmsManager();
4006 mRespondViaSmsManager.setInCallScreenInstance(this);
4007 }
4008
4009 /**
4010 * Updates the state of the in-call touch UI.
4011 */
4012 private void updateInCallTouchUi() {
4013 if (mInCallTouchUi != null) {
4014 mInCallTouchUi.updateState(mCM);
4015 }
4016 }
4017
4018 /**
4019 * @return the InCallTouchUi widget
4020 */
4021 /* package */ InCallTouchUi getInCallTouchUi() {
4022 return mInCallTouchUi;
4023 }
4024
4025 /**
4026 * Posts a handler message telling the InCallScreen to refresh the
4027 * onscreen in-call UI.
4028 *
4029 * This is just a wrapper around updateScreen(), for use by the
4030 * rest of the phone app or from a thread other than the UI thread.
4031 *
4032 * updateScreen() is a no-op if the InCallScreen is not the foreground
4033 * activity, so it's safe to call this whether or not the InCallScreen
4034 * is currently visible.
4035 */
4036 /* package */ void requestUpdateScreen() {
4037 if (DBG) log("requestUpdateScreen()...");
4038 mHandler.removeMessages(REQUEST_UPDATE_SCREEN);
4039 mHandler.sendEmptyMessage(REQUEST_UPDATE_SCREEN);
4040 }
4041
4042 /**
4043 * @return true if we're in restricted / emergency dialing only mode.
4044 */
4045 public boolean isPhoneStateRestricted() {
4046 // TODO: This needs to work IN TANDEM with the KeyGuardViewMediator Code.
4047 // Right now, it looks like the mInputRestricted flag is INTERNAL to the
4048 // KeyGuardViewMediator and SPECIFICALLY set to be FALSE while the emergency
4049 // phone call is being made, to allow for input into the InCallScreen.
4050 // Having the InCallScreen judge the state of the device from this flag
4051 // becomes meaningless since it is always false for us. The mediator should
4052 // have an additional API to let this app know that it should be restricted.
4053 int serviceState = mCM.getServiceState();
4054 return ((serviceState == ServiceState.STATE_EMERGENCY_ONLY) ||
4055 (serviceState == ServiceState.STATE_OUT_OF_SERVICE) ||
4056 (mApp.getKeyguardManager().inKeyguardRestrictedInputMode()));
4057 }
4058
4059
4060 //
4061 // Bluetooth helper methods.
4062 //
4063 // - BluetoothAdapter is the Bluetooth system service. If
4064 // getDefaultAdapter() returns null
4065 // then the device is not BT capable. Use BluetoothDevice.isEnabled()
4066 // to see if BT is enabled on the device.
4067 //
4068 // - BluetoothHeadset is the API for the control connection to a
4069 // Bluetooth Headset. This lets you completely connect/disconnect a
4070 // headset (which we don't do from the Phone UI!) but also lets you
4071 // get the address of the currently active headset and see whether
4072 // it's currently connected.
4073
4074 /**
4075 * @return true if the Bluetooth on/off switch in the UI should be
4076 * available to the user (i.e. if the device is BT-capable
4077 * and a headset is connected.)
4078 */
4079 /* package */ boolean isBluetoothAvailable() {
4080 if (VDBG) log("isBluetoothAvailable()...");
4081
4082 // There's no need to ask the Bluetooth system service if BT is enabled:
4083 //
4084 // BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
4085 // if ((adapter == null) || !adapter.isEnabled()) {
4086 // if (DBG) log(" ==> FALSE (BT not enabled)");
4087 // return false;
4088 // }
4089 // if (DBG) log(" - BT enabled! device name " + adapter.getName()
4090 // + ", address " + adapter.getAddress());
4091 //
4092 // ...since we already have a BluetoothHeadset instance. We can just
4093 // call isConnected() on that, and assume it'll be false if BT isn't
4094 // enabled at all.
4095
4096 // Check if there's a connected headset, using the BluetoothHeadset API.
4097 boolean isConnected = false;
4098 if (mBluetoothHeadset != null) {
4099 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
4100
4101 if (deviceList.size() > 0) {
4102 BluetoothDevice device = deviceList.get(0);
4103 isConnected = true;
4104
4105 if (VDBG) log(" - headset state = " +
4106 mBluetoothHeadset.getConnectionState(device));
4107 if (VDBG) log(" - headset address: " + device);
4108 if (VDBG) log(" - isConnected: " + isConnected);
4109 }
4110 }
4111
4112 if (VDBG) log(" ==> " + isConnected);
4113 return isConnected;
4114 }
4115
4116 /**
4117 * @return true if a BT Headset is available, and its audio is currently connected.
4118 */
4119 /* package */ boolean isBluetoothAudioConnected() {
4120 if (mBluetoothHeadset == null) {
4121 if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
4122 return false;
4123 }
4124 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
4125
4126 if (deviceList.isEmpty()) {
4127 return false;
4128 }
4129 BluetoothDevice device = deviceList.get(0);
4130 boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
4131 if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
4132 return isAudioOn;
4133 }
4134
4135 /**
4136 * Helper method used to control the onscreen "Bluetooth" indication;
4137 * see InCallControlState.bluetoothIndicatorOn.
4138 *
4139 * @return true if a BT device is available and its audio is currently connected,
4140 * <b>or</b> if we issued a BluetoothHeadset.connectAudio()
4141 * call within the last 5 seconds (which presumably means
4142 * that the BT audio connection is currently being set
4143 * up, and will be connected soon.)
4144 */
4145 /* package */ boolean isBluetoothAudioConnectedOrPending() {
4146 if (isBluetoothAudioConnected()) {
4147 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
4148 return true;
4149 }
4150
4151 // If we issued a connectAudio() call "recently enough", even
4152 // if BT isn't actually connected yet, let's still pretend BT is
4153 // on. This makes the onscreen indication more responsive.
4154 if (mBluetoothConnectionPending) {
4155 long timeSinceRequest =
4156 SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
4157 if (timeSinceRequest < 5000 /* 5 seconds */) {
4158 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
4159 + timeSinceRequest + " msec ago)");
4160 return true;
4161 } else {
4162 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
4163 + timeSinceRequest + " msec ago)");
4164 mBluetoothConnectionPending = false;
4165 return false;
4166 }
4167 }
4168
4169 if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
4170 return false;
4171 }
4172
4173 /**
4174 * Posts a message to our handler saying to update the onscreen UI
4175 * based on a bluetooth headset state change.
4176 */
4177 /* package */ void requestUpdateBluetoothIndication() {
4178 if (VDBG) log("requestUpdateBluetoothIndication()...");
4179 // No need to look at the current state here; any UI elements that
4180 // care about the bluetooth state (i.e. the CallCard) get
4181 // the necessary state directly from PhoneApp.showBluetoothIndication().
4182 mHandler.removeMessages(REQUEST_UPDATE_BLUETOOTH_INDICATION);
4183 mHandler.sendEmptyMessage(REQUEST_UPDATE_BLUETOOTH_INDICATION);
4184 }
4185
4186 private void dumpBluetoothState() {
4187 log("============== dumpBluetoothState() =============");
4188 log("= isBluetoothAvailable: " + isBluetoothAvailable());
4189 log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
4190 log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
4191 log("= PhoneApp.showBluetoothIndication: "
4192 + mApp.showBluetoothIndication());
4193 log("=");
4194 if (mBluetoothAdapter != null) {
4195 if (mBluetoothHeadset != null) {
4196 List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
4197
4198 if (deviceList.size() > 0) {
4199 BluetoothDevice device = deviceList.get(0);
4200 log("= BluetoothHeadset.getCurrentDevice: " + device);
4201 log("= BluetoothHeadset.State: "
4202 + mBluetoothHeadset.getConnectionState(device));
4203 log("= BluetoothHeadset audio connected: " +
4204 mBluetoothHeadset.isAudioConnected(device));
4205 }
4206 } else {
4207 log("= mBluetoothHeadset is null");
4208 }
4209 } else {
4210 log("= mBluetoothAdapter is null; device is not BT capable");
4211 }
4212 }
4213
4214 /* package */ void connectBluetoothAudio() {
4215 if (VDBG) log("connectBluetoothAudio()...");
4216 if (mBluetoothHeadset != null) {
4217 // TODO(BT) check return
4218 mBluetoothHeadset.connectAudio();
4219 }
4220
4221 // Watch out: The bluetooth connection doesn't happen instantly;
4222 // the connectAudio() call returns instantly but does its real
4223 // work in another thread. The mBluetoothConnectionPending flag
4224 // is just a little trickery to ensure that the onscreen UI updates
4225 // instantly. (See isBluetoothAudioConnectedOrPending() above.)
4226 mBluetoothConnectionPending = true;
4227 mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
4228 }
4229
4230 /* package */ void disconnectBluetoothAudio() {
4231 if (VDBG) log("disconnectBluetoothAudio()...");
4232 if (mBluetoothHeadset != null) {
4233 mBluetoothHeadset.disconnectAudio();
4234 }
4235 mBluetoothConnectionPending = false;
4236 }
4237
4238 /**
4239 * Posts a handler message telling the InCallScreen to close
4240 * the OTA failure notice after the specified delay.
4241 * @see OtaUtils.otaShowProgramFailureNotice
4242 */
4243 /* package */ void requestCloseOtaFailureNotice(long timeout) {
4244 if (DBG) log("requestCloseOtaFailureNotice() with timeout: " + timeout);
4245 mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_OTA_FAILURE_NOTICE, timeout);
4246
4247 // TODO: we probably ought to call removeMessages() for this
4248 // message code in either onPause or onResume, just to be 100%
4249 // sure that the message we just posted has no way to affect a
4250 // *different* call if the user quickly backs out and restarts.
4251 // (This is also true for requestCloseSpcErrorNotice() below, and
4252 // probably anywhere else we use mHandler.sendEmptyMessageDelayed().)
4253 }
4254
4255 /**
4256 * Posts a handler message telling the InCallScreen to close
4257 * the SPC error notice after the specified delay.
4258 * @see OtaUtils.otaShowSpcErrorNotice
4259 */
4260 /* package */ void requestCloseSpcErrorNotice(long timeout) {
4261 if (DBG) log("requestCloseSpcErrorNotice() with timeout: " + timeout);
4262 mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_SPC_ERROR_NOTICE, timeout);
4263 }
4264
4265 public boolean isOtaCallInActiveState() {
4266 if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
4267 || ((mApp.cdmaOtaScreenState != null)
4268 && (mApp.cdmaOtaScreenState.otaScreenState ==
4269 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
4270 return true;
4271 } else {
4272 return false;
4273 }
4274 }
4275
4276 /**
4277 * Handle OTA Call End scenario when display becomes dark during OTA Call
4278 * and InCallScreen is in pause mode. CallNotifier will listen for call
4279 * end indication and call this api to handle OTA Call end scenario
4280 */
4281 public void handleOtaCallEnd() {
4282 if (DBG) log("handleOtaCallEnd entering");
4283 if (((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
4284 || ((mApp.cdmaOtaScreenState != null)
4285 && (mApp.cdmaOtaScreenState.otaScreenState !=
4286 CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))
4287 && ((mApp.cdmaOtaProvisionData != null)
4288 && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
4289 if (DBG) log("handleOtaCallEnd - Set OTA Call End stater");
4290 setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4291 updateScreen();
4292 }
4293 }
4294
4295 public boolean isOtaCallInEndState() {
4296 return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED);
4297 }
4298
4299
4300 /**
4301 * Upon resuming the in-call UI, check to see if an OTASP call is in
4302 * progress, and if so enable the special OTASP-specific UI.
4303 *
4304 * TODO: have a simple single flag in InCallUiState for this rather than
4305 * needing to know about all those mApp.cdma*State objects.
4306 *
4307 * @return true if any OTASP-related UI is active
4308 */
4309 private boolean checkOtaspStateOnResume() {
4310 // If there's no OtaUtils instance, that means we haven't even tried
4311 // to start an OTASP call (yet), so there's definitely nothing to do here.
4312 if (mApp.otaUtils == null) {
4313 if (DBG) log("checkOtaspStateOnResume: no OtaUtils instance; nothing to do.");
4314 return false;
4315 }
4316
4317 if ((mApp.cdmaOtaScreenState == null) || (mApp.cdmaOtaProvisionData == null)) {
4318 // Uh oh -- something wrong with our internal OTASP state.
4319 // (Since this is an OTASP-capable device, these objects
4320 // *should* have already been created by PhoneApp.onCreate().)
4321 throw new IllegalStateException("checkOtaspStateOnResume: "
4322 + "app.cdmaOta* objects(s) not initialized");
4323 }
4324
4325 // The PhoneApp.cdmaOtaInCallScreenUiState instance is the
4326 // authoritative source saying whether or not the in-call UI should
4327 // show its OTASP-related UI.
4328
4329 OtaUtils.CdmaOtaInCallScreenUiState.State cdmaOtaInCallScreenState =
4330 mApp.otaUtils.getCdmaOtaInCallScreenUiState();
4331 // These states are:
4332 // - UNDEFINED: no OTASP-related UI is visible
4333 // - NORMAL: OTASP call in progress, so show in-progress OTASP UI
4334 // - ENDED: OTASP call just ended, so show success/failure indication
4335
4336 boolean otaspUiActive =
4337 (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL)
4338 || (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
4339
4340 if (otaspUiActive) {
4341 // Make sure the OtaUtils instance knows about the InCallScreen's
4342 // OTASP-related UI widgets.
4343 //
4344 // (This call has no effect if the UI widgets have already been set up.
4345 // It only really matters the very first time that the InCallScreen instance
4346 // is onResume()d after starting an OTASP call.)
4347 mApp.otaUtils.updateUiWidgets(this, mInCallTouchUi, mCallCard);
4348
4349 // Also update the InCallScreenMode based on the cdmaOtaInCallScreenState.
4350
4351 if (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL) {
4352 if (DBG) log("checkOtaspStateOnResume - in OTA Normal mode");
4353 setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
4354 } else if (cdmaOtaInCallScreenState ==
4355 OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED) {
4356 if (DBG) log("checkOtaspStateOnResume - in OTA END mode");
4357 setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4358 }
4359
4360 // TODO(OTASP): we might also need to go into OTA_ENDED mode
4361 // in one extra case:
4362 //
4363 // else if (mApp.cdmaOtaScreenState.otaScreenState ==
4364 // CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG) {
4365 // if (DBG) log("checkOtaspStateOnResume - set OTA END Mode");
4366 // setInCallScreenMode(InCallScreenMode.OTA_ENDED);
4367 // }
4368
4369 } else {
4370 // OTASP is not active; reset to regular in-call UI.
4371
4372 if (DBG) log("checkOtaspStateOnResume - Set OTA NORMAL Mode");
4373 setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
4374
4375 if (mApp.otaUtils != null) {
4376 mApp.otaUtils.cleanOtaScreen(false);
4377 }
4378 }
4379
4380 // TODO(OTASP):
4381 // The original check from checkIsOtaCall() when handling ACTION_MAIN was this:
4382 //
4383 // [ . . . ]
4384 // else if (action.equals(intent.ACTION_MAIN)) {
4385 // if (DBG) log("checkIsOtaCall action ACTION_MAIN");
4386 // boolean isRingingCall = mCM.hasActiveRingingCall();
4387 // if (isRingingCall) {
4388 // if (DBG) log("checkIsOtaCall isRingingCall: " + isRingingCall);
4389 // return false;
4390 // } else if ((mApp.cdmaOtaInCallScreenUiState.state
4391 // == CdmaOtaInCallScreenUiState.State.NORMAL)
4392 // || (mApp.cdmaOtaInCallScreenUiState.state
4393 // == CdmaOtaInCallScreenUiState.State.ENDED)) {
4394 // if (DBG) log("action ACTION_MAIN, OTA call already in progress");
4395 // isOtaCall = true;
4396 // } else {
4397 // if (mApp.cdmaOtaScreenState.otaScreenState !=
4398 // CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED) {
4399 // if (DBG) log("checkIsOtaCall action ACTION_MAIN, "
4400 // + "OTA call in progress with UNDEFINED");
4401 // isOtaCall = true;
4402 // }
4403 // }
4404 // }
4405 //
4406 // Also, in internalResolveIntent() we used to do this:
4407 //
4408 // if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
4409 // || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)) {
4410 // // If in OTA Call, update the OTA UI
4411 // updateScreen();
4412 // return;
4413 // }
4414 //
4415 // We still need more cleanup to simplify the mApp.cdma*State objects.
4416
4417 return otaspUiActive;
4418 }
4419
4420 /**
4421 * Updates and returns the InCallControlState instance.
4422 */
4423 public InCallControlState getUpdatedInCallControlState() {
4424 if (VDBG) log("getUpdatedInCallControlState()...");
4425 mInCallControlState.update();
4426 return mInCallControlState;
4427 }
4428
4429 public void resetInCallScreenMode() {
4430 if (DBG) log("resetInCallScreenMode: setting mode to UNDEFINED...");
4431 setInCallScreenMode(InCallScreenMode.UNDEFINED);
4432 }
4433
4434 /**
4435 * Updates the onscreen hint displayed while the user is dragging one
4436 * of the handles of the RotarySelector widget used for incoming
4437 * calls.
4438 *
4439 * @param hintTextResId resource ID of the hint text to display,
4440 * or 0 if no hint should be visible.
4441 * @param hintColorResId resource ID for the color of the hint text
4442 */
4443 /* package */ void updateIncomingCallWidgetHint(int hintTextResId, int hintColorResId) {
4444 if (VDBG) log("updateIncomingCallWidgetHint(" + hintTextResId + ")...");
4445 if (mCallCard != null) {
4446 mCallCard.setIncomingCallWidgetHint(hintTextResId, hintColorResId);
4447 mCallCard.updateState(mCM);
4448 // TODO: if hintTextResId == 0, consider NOT clearing the onscreen
4449 // hint right away, but instead post a delayed handler message to
4450 // keep it onscreen for an extra second or two. (This might make
4451 // the hint more helpful if the user quickly taps one of the
4452 // handles without dragging at all...)
4453 // (Or, maybe this should happen completely within the RotarySelector
4454 // widget, since the widget itself probably wants to keep the colored
4455 // arrow visible for some extra time also...)
4456 }
4457 }
4458
4459
4460 /**
4461 * Used when we need to update buttons outside InCallTouchUi's updateInCallControls() along
4462 * with that method being called. CallCard may call this too because it doesn't have
4463 * enough information to update buttons inside itself (more specifically, the class cannot
4464 * obtain mInCallControllState without some side effect. See also
4465 * {@link #getUpdatedInCallControlState()}. We probably don't want a method like
4466 * getRawCallControlState() which returns raw intance with no side effect just for this
4467 * corner case scenario)
4468 *
4469 * TODO: need better design for buttons outside InCallTouchUi.
4470 */
4471 /* package */ void updateButtonStateOutsideInCallTouchUi() {
4472 if (mCallCard != null) {
4473 mCallCard.setSecondaryCallClickable(mInCallControlState.canSwap);
4474 }
4475 }
4476
4477 @Override
4478 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
4479 super.dispatchPopulateAccessibilityEvent(event);
4480 mCallCard.dispatchPopulateAccessibilityEvent(event);
4481 return true;
4482 }
4483
4484 /**
4485 * Manually handle configuration changes.
4486 *
4487 * Originally android:configChanges was set to "orientation|keyboardHidden|uiMode"
4488 * in order "to make sure the system doesn't destroy and re-create us due to the
4489 * above config changes". However it is currently set to "keyboardHidden" since
4490 * the system needs to handle rotation when inserted into a compatible cardock.
4491 * Even without explicitly handling orientation and uiMode, the app still runs
4492 * and does not drop the call when rotated.
4493 *
4494 */
4495 public void onConfigurationChanged(Configuration newConfig) {
4496 if (DBG) log("onConfigurationChanged: newConfig = " + newConfig);
4497
4498 // Note: At the time this function is called, our Resources object
4499 // will have already been updated to return resource values matching
4500 // the new configuration.
4501
4502 // Watch out: we *can* still get destroyed and recreated if a
4503 // configuration change occurs that is *not* listed in the
4504 // android:configChanges attribute. TODO: Any others we need to list?
4505
4506 super.onConfigurationChanged(newConfig);
4507
4508 // Nothing else to do here, since (currently) the InCallScreen looks
4509 // exactly the same regardless of configuration.
4510 // (Specifically, we'll never be in landscape mode because we set
4511 // android:screenOrientation="portrait" in our manifest, and we don't
4512 // change our UI at all based on newConfig.keyboardHidden or
4513 // newConfig.uiMode.)
4514
4515 // TODO: we do eventually want to handle at least some config changes, such as:
4516 boolean isKeyboardOpen = (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO);
4517 if (DBG) log(" - isKeyboardOpen = " + isKeyboardOpen);
4518 boolean isLandscape = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
4519 if (DBG) log(" - isLandscape = " + isLandscape);
4520 if (DBG) log(" - uiMode = " + newConfig.uiMode);
4521 // See bug 2089513.
4522 }
4523
4524 /**
4525 * Handles an incoming RING event from the telephony layer.
4526 */
4527 private void onIncomingRing() {
4528 if (DBG) log("onIncomingRing()...");
4529 // IFF we're visible, forward this event to the InCallTouchUi
4530 // instance (which uses this event to drive the animation of the
4531 // incoming-call UI.)
4532 if (mIsForegroundActivity && (mInCallTouchUi != null)) {
4533 mInCallTouchUi.onIncomingRing();
4534 }
4535 }
4536
4537 /**
4538 * Handles a "new ringing connection" event from the telephony layer.
4539 *
4540 * This event comes in right at the start of the incoming-call sequence,
4541 * exactly once per incoming call.
4542 *
4543 * Watch out: this won't be called if InCallScreen isn't ready yet,
4544 * which typically happens for the first incoming phone call (even before
4545 * the possible first outgoing call).
4546 */
4547 private void onNewRingingConnection() {
4548 if (DBG) log("onNewRingingConnection()...");
4549
4550 // We use this event to reset any incoming-call-related UI elements
4551 // that might have been left in an inconsistent state after a prior
4552 // incoming call.
4553 // (Note we do this whether or not we're the foreground activity,
4554 // since this event comes in *before* we actually get launched to
4555 // display the incoming-call UI.)
4556
4557 // If there's a "Respond via SMS" popup still around since the
4558 // last time we were the foreground activity, make sure it's not
4559 // still active(!) since that would interfere with *this* incoming
4560 // call.
4561 // (Note that we also do this same check in onResume(). But we
4562 // need it here too, to make sure the popup gets reset in the case
4563 // where a call-waiting call comes in while the InCallScreen is
4564 // already in the foreground.)
4565 mRespondViaSmsManager.dismissPopup(); // safe even if already dismissed
4566 }
4567
4568 /**
4569 * Enables or disables the status bar "window shade" based on the current situation.
4570 */
4571 private void updateExpandedViewState() {
4572 if (mIsForegroundActivity) {
4573 if (mApp.proximitySensorModeEnabled()) {
4574 // We should not enable notification's expanded view on RINGING state.
4575 mApp.notificationMgr.statusBarHelper.enableExpandedView(
4576 mCM.getState() != PhoneConstants.State.RINGING);
4577 } else {
4578 // If proximity sensor is unavailable on the device, disable it to avoid false
4579 // touches toward notifications.
4580 mApp.notificationMgr.statusBarHelper.enableExpandedView(false);
4581 }
4582 } else {
4583 mApp.notificationMgr.statusBarHelper.enableExpandedView(true);
4584 }
4585 }
4586
4587 private void log(String msg) {
4588 Log.d(LOG_TAG, msg);
4589 }
4590
4591 /**
4592 * Requests to remove provider info frame after having
4593 * {@link #PROVIDER_INFO_TIMEOUT}) msec delay.
4594 */
4595 /* package */ void requestRemoveProviderInfoWithDelay() {
4596 // Remove any zombie messages and then send a message to
4597 // self to remove the provider info after some time.
4598 mHandler.removeMessages(EVENT_HIDE_PROVIDER_INFO);
4599 Message msg = Message.obtain(mHandler, EVENT_HIDE_PROVIDER_INFO);
4600 mHandler.sendMessageDelayed(msg, PROVIDER_INFO_TIMEOUT);
4601 if (DBG) {
4602 log("Requested to remove provider info after " + PROVIDER_INFO_TIMEOUT + " msec.");
4603 }
4604 }
4605
4606 /**
4607 * Indicates whether or not the QuickResponseDialog is currently showing in the call screen
4608 */
4609 public boolean isQuickResponseDialogShowing() {
4610 return mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
4611 }
4612}