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