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