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