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