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