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