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