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