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