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