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