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