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