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