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.incallui; 18 19 import android.app.ActionBar; 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.app.FragmentManager; 23 import android.app.FragmentTransaction; 24 import android.content.DialogInterface; 25 import android.content.DialogInterface.OnClickListener; 26 import android.content.DialogInterface.OnCancelListener; 27 import android.content.Intent; 28 import android.content.res.Configuration; 29 import android.graphics.Point; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.telecom.DisconnectCause; 33 import android.telecom.PhoneAccount; 34 import android.telecom.PhoneAccountHandle; 35 import android.telephony.PhoneNumberUtils; 36 import android.text.TextUtils; 37 import android.view.MenuItem; 38 import android.view.animation.Animation; 39 import android.view.animation.AnimationUtils; 40 import android.view.KeyEvent; 41 import android.view.View; 42 import android.view.Window; 43 import android.view.WindowManager; 44 import android.view.accessibility.AccessibilityEvent; 45 46 import com.android.phone.common.animation.AnimUtils; 47 import com.android.phone.common.animation.AnimationListenerAdapter; 48 import com.android.contacts.common.interactions.TouchPointManager; 49 import com.android.incallui.Call.State; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Locale; 54 55 /** 56 * Phone app "in call" screen. 57 */ 58 public class InCallActivity extends Activity { 59 60 public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad"; 61 public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text"; 62 public static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call"; 63 64 private CallButtonFragment mCallButtonFragment; 65 private CallCardFragment mCallCardFragment; 66 private AnswerFragment mAnswerFragment; 67 private DialpadFragment mDialpadFragment; 68 private ConferenceManagerFragment mConferenceManagerFragment; 69 private FragmentManager mChildFragmentManager; 70 71 private boolean mIsForegroundActivity; 72 private AlertDialog mDialog; 73 74 /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */ 75 private boolean mShowDialpadRequested; 76 77 /** Use to determine if the dialpad should be animated on show. */ 78 private boolean mAnimateDialpadOnShow; 79 80 /** Use to determine the DTMF Text which should be pre-populated in the dialpad. */ 81 private String mDtmfText; 82 83 /** Use to pass parameters for showing the PostCharDialog to {@link #onResume} */ 84 private boolean mShowPostCharWaitDialogOnResume; 85 private String mShowPostCharWaitDialogCallId; 86 private String mShowPostCharWaitDialogChars; 87 88 private boolean mIsLandscape; 89 private Animation mSlideIn; 90 private Animation mSlideOut; 91 AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() { 92 @Override 93 public void onAnimationEnd(Animation animation) { 94 showDialpad(false); 95 } 96 }; 97 98 /** 99 * Stores the current orientation of the activity. Used to determine if a change in orientation 100 * has occurred. 101 */ 102 private int mCurrentOrientation; 103 104 @Override 105 protected void onCreate(Bundle icicle) { 106 Log.d(this, "onCreate()... this = " + this); 107 108 super.onCreate(icicle); 109 110 // set this flag so this activity will stay in front of the keyguard 111 // Have the WindowManager filter out touch events that are "too fat". 112 int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 113 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON 114 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 115 116 getWindow().addFlags(flags); 117 118 // Setup action bar for the conference call manager. 119 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 120 ActionBar actionBar = getActionBar(); 121 if (actionBar != null) { 122 actionBar.setDisplayHomeAsUpEnabled(true); 123 actionBar.setDisplayShowTitleEnabled(true); 124 actionBar.hide(); 125 } 126 127 // TODO(klp): Do we need to add this back when prox sensor is not available? 128 // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; 129 130 // Inflate everything in incall_screen.xml and add it to the screen. 131 setContentView(R.layout.incall_screen); 132 133 initializeInCall(); 134 135 internalResolveIntent(getIntent()); 136 137 mCurrentOrientation = getResources().getConfiguration().orientation; 138 mIsLandscape = getResources().getConfiguration().orientation 139 == Configuration.ORIENTATION_LANDSCAPE; 140 141 final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == 142 View.LAYOUT_DIRECTION_RTL; 143 144 if (mIsLandscape) { 145 mSlideIn = AnimationUtils.loadAnimation(this, 146 isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right); 147 mSlideOut = AnimationUtils.loadAnimation(this, 148 isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right); 149 } else { 150 mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom); 151 mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom); 152 } 153 154 mSlideIn.setInterpolator(AnimUtils.EASE_IN); 155 mSlideOut.setInterpolator(AnimUtils.EASE_OUT); 156 157 mSlideOut.setAnimationListener(mSlideOutListener); 158 159 if (icicle != null) { 160 // If the dialpad was shown before, set variables indicating it should be shown and 161 // populated with the previous DTMF text. The dialpad is actually shown and populated 162 // in onResume() to ensure the hosting CallCardFragment has been inflated and is ready 163 // to receive it. 164 mShowDialpadRequested = icicle.getBoolean(SHOW_DIALPAD_EXTRA); 165 mAnimateDialpadOnShow = false; 166 mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA); 167 } 168 Log.d(this, "onCreate(): exit"); 169 } 170 171 @Override 172 protected void onSaveInstanceState(Bundle out) { 173 out.putBoolean(SHOW_DIALPAD_EXTRA, mCallButtonFragment.isDialpadVisible()); 174 if (mDialpadFragment != null) { 175 out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText()); 176 } 177 } 178 179 @Override 180 protected void onStart() { 181 Log.d(this, "onStart()..."); 182 super.onStart(); 183 184 // setting activity should be last thing in setup process 185 InCallPresenter.getInstance().setActivity(this); 186 } 187 188 @Override 189 protected void onResume() { 190 Log.i(this, "onResume()..."); 191 super.onResume(); 192 193 mIsForegroundActivity = true; 194 InCallPresenter.getInstance().onUiShowing(true); 195 196 if (mShowDialpadRequested) { 197 mCallButtonFragment.displayDialpad(true /* show */, 198 mAnimateDialpadOnShow /* animate */); 199 mShowDialpadRequested = false; 200 mAnimateDialpadOnShow = false; 201 202 if (mDialpadFragment != null) { 203 mDialpadFragment.setDtmfText(mDtmfText); 204 mDtmfText = null; 205 } 206 } 207 208 if (mShowPostCharWaitDialogOnResume) { 209 showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars); 210 } 211 } 212 213 // onPause is guaranteed to be called when the InCallActivity goes 214 // in the background. 215 @Override 216 protected void onPause() { 217 Log.d(this, "onPause()..."); 218 super.onPause(); 219 220 mIsForegroundActivity = false; 221 222 if (mDialpadFragment != null ) { 223 mDialpadFragment.onDialerKeyUp(null); 224 } 225 226 InCallPresenter.getInstance().onUiShowing(false); 227 } 228 229 @Override 230 protected void onStop() { 231 Log.d(this, "onStop()..."); 232 super.onStop(); 233 } 234 235 @Override 236 protected void onDestroy() { 237 Log.d(this, "onDestroy()... this = " + this); 238 239 InCallPresenter.getInstance().setActivity(null); 240 241 super.onDestroy(); 242 } 243 244 /** 245 * Returns true when theActivity is in foreground (between onResume and onPause). 246 */ 247 /* package */ boolean isForegroundActivity() { 248 return mIsForegroundActivity; 249 } 250 251 private boolean hasPendingErrorDialog() { 252 return mDialog != null; 253 } 254 255 /** 256 * Dismisses the in-call screen. 257 * 258 * We never *really* finish() the InCallActivity, since we don't want to get destroyed and then 259 * have to be re-created from scratch for the next call. Instead, we just move ourselves to the 260 * back of the activity stack. 261 * 262 * This also means that we'll no longer be reachable via the BACK button (since moveTaskToBack() 263 * puts us behind the Home app, but the home app doesn't allow the BACK key to move you any 264 * farther down in the history stack.) 265 * 266 * (Since the Phone app itself is never killed, this basically means that we'll keep a single 267 * InCallActivity instance around for the entire uptime of the device. This noticeably improves 268 * the UI responsiveness for incoming calls.) 269 */ 270 @Override 271 public void finish() { 272 Log.i(this, "finish(). Dialog showing: " + (mDialog != null)); 273 274 // skip finish if we are still showing a dialog. 275 if (!hasPendingErrorDialog() && !mAnswerFragment.hasPendingDialogs()) { 276 super.finish(); 277 } 278 } 279 280 @Override 281 protected void onNewIntent(Intent intent) { 282 Log.d(this, "onNewIntent: intent = " + intent); 283 284 // We're being re-launched with a new Intent. Since it's possible for a 285 // single InCallActivity instance to persist indefinitely (even if we 286 // finish() ourselves), this sequence can potentially happen any time 287 // the InCallActivity needs to be displayed. 288 289 // Stash away the new intent so that we can get it in the future 290 // by calling getIntent(). (Otherwise getIntent() will return the 291 // original Intent from when we first got created!) 292 setIntent(intent); 293 294 // Activities are always paused before receiving a new intent, so 295 // we can count on our onResume() method being called next. 296 297 // Just like in onCreate(), handle the intent. 298 internalResolveIntent(intent); 299 } 300 301 @Override 302 public void onBackPressed() { 303 Log.d(this, "onBackPressed()..."); 304 305 // BACK is also used to exit out of any "special modes" of the 306 // in-call UI: 307 308 if (!mCallCardFragment.isVisible()) { 309 return; 310 } 311 312 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 313 mCallButtonFragment.displayDialpad(false /* show */, true /* animate */); 314 return; 315 } else if (mConferenceManagerFragment.isVisible()) { 316 mConferenceManagerFragment.setVisible(false); 317 return; 318 } 319 320 // Always disable the Back key while an incoming call is ringing 321 final Call call = CallList.getInstance().getIncomingCall(); 322 if (call != null) { 323 Log.d(this, "Consume Back press for an incoming call"); 324 return; 325 } 326 327 // Nothing special to do. Fall back to the default behavior. 328 super.onBackPressed(); 329 } 330 331 @Override 332 public boolean onOptionsItemSelected(MenuItem item) { 333 final int itemId = item.getItemId(); 334 if (itemId == android.R.id.home) { 335 onBackPressed(); 336 return true; 337 } 338 return super.onOptionsItemSelected(item); 339 } 340 341 @Override 342 public boolean onKeyUp(int keyCode, KeyEvent event) { 343 // push input to the dialer. 344 if (mDialpadFragment != null && (mDialpadFragment.isVisible()) && 345 (mDialpadFragment.onDialerKeyUp(event))){ 346 return true; 347 } else if (keyCode == KeyEvent.KEYCODE_CALL) { 348 // Always consume CALL to be sure the PhoneWindow won't do anything with it 349 return true; 350 } 351 return super.onKeyUp(keyCode, event); 352 } 353 354 @Override 355 public boolean onKeyDown(int keyCode, KeyEvent event) { 356 switch (keyCode) { 357 case KeyEvent.KEYCODE_CALL: 358 boolean handled = InCallPresenter.getInstance().handleCallKey(); 359 if (!handled) { 360 Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown"); 361 } 362 // Always consume CALL to be sure the PhoneWindow won't do anything with it 363 return true; 364 365 // Note there's no KeyEvent.KEYCODE_ENDCALL case here. 366 // The standard system-wide handling of the ENDCALL key 367 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL) 368 // already implements exactly what the UI spec wants, 369 // namely (1) "hang up" if there's a current active call, 370 // or (2) "don't answer" if there's a current ringing call. 371 372 case KeyEvent.KEYCODE_CAMERA: 373 // Disable the CAMERA button while in-call since it's too 374 // easy to press accidentally. 375 return true; 376 377 case KeyEvent.KEYCODE_VOLUME_UP: 378 case KeyEvent.KEYCODE_VOLUME_DOWN: 379 case KeyEvent.KEYCODE_VOLUME_MUTE: 380 // Ringer silencing handled by PhoneWindowManager. 381 break; 382 383 case KeyEvent.KEYCODE_MUTE: 384 // toggle mute 385 TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute()); 386 return true; 387 388 // Various testing/debugging features, enabled ONLY when VERBOSE == true. 389 case KeyEvent.KEYCODE_SLASH: 390 if (Log.VERBOSE) { 391 Log.v(this, "----------- InCallActivity View dump --------------"); 392 // Dump starting from the top-level view of the entire activity: 393 Window w = this.getWindow(); 394 View decorView = w.getDecorView(); 395 Log.d(this, "View dump:" + decorView); 396 return true; 397 } 398 break; 399 case KeyEvent.KEYCODE_EQUALS: 400 // TODO: Dump phone state? 401 break; 402 } 403 404 if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) { 405 return true; 406 } 407 408 return super.onKeyDown(keyCode, event); 409 } 410 411 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) { 412 Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "..."); 413 414 // As soon as the user starts typing valid dialable keys on the 415 // keyboard (presumably to type DTMF tones) we start passing the 416 // key events to the DTMFDialer's onDialerKeyDown. 417 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 418 return mDialpadFragment.onDialerKeyDown(event); 419 420 // TODO: If the dialpad isn't currently visible, maybe 421 // consider automatically bringing it up right now? 422 // (Just to make sure the user sees the digits widget...) 423 // But this probably isn't too critical since it's awkward to 424 // use the hard keyboard while in-call in the first place, 425 // especially now that the in-call UI is portrait-only... 426 } 427 428 return false; 429 } 430 431 @Override 432 public void onConfigurationChanged(Configuration config) { 433 InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config); 434 Log.d(this, "onConfigurationChanged "+config.orientation); 435 436 // Check to see if the orientation changed to prevent triggering orientation change events 437 // for other configuration changes. 438 if (config.orientation != mCurrentOrientation) { 439 mCurrentOrientation = config.orientation; 440 InCallPresenter.getInstance().onDeviceRotationChange( 441 getWindowManager().getDefaultDisplay().getRotation()); 442 InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation); 443 } 444 super.onConfigurationChanged(config); 445 } 446 447 public CallButtonFragment getCallButtonFragment() { 448 return mCallButtonFragment; 449 } 450 451 public CallCardFragment getCallCardFragment() { 452 return mCallCardFragment; 453 } 454 455 private void internalResolveIntent(Intent intent) { 456 final String action = intent.getAction(); 457 458 if (action.equals(intent.ACTION_MAIN)) { 459 // This action is the normal way to bring up the in-call UI. 460 // 461 // But we do check here for one extra that can come along with the 462 // ACTION_MAIN intent: 463 464 if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) { 465 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF 466 // dialpad should be initially visible. If the extra isn't 467 // present at all, we just leave the dialpad in its previous state. 468 469 final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false); 470 Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad); 471 472 relaunchedFromDialer(showDialpad); 473 } 474 475 if (intent.getBooleanExtra(NEW_OUTGOING_CALL, false)) { 476 intent.removeExtra(NEW_OUTGOING_CALL); 477 Call call = CallList.getInstance().getOutgoingCall(); 478 if (call == null) { 479 call = CallList.getInstance().getPendingOutgoingCall(); 480 } 481 482 Bundle extras = null; 483 if (call != null) { 484 extras = call.getTelecommCall().getDetails().getExtras(); 485 } 486 if (extras == null) { 487 // Initialize the extras bundle to avoid NPE 488 extras = new Bundle(); 489 } 490 491 492 Point touchPoint = null; 493 if (TouchPointManager.getInstance().hasValidPoint()) { 494 // Use the most immediate touch point in the InCallUi if available 495 touchPoint = TouchPointManager.getInstance().getPoint(); 496 } else { 497 // Otherwise retrieve the touch point from the call intent 498 if (call != null) { 499 touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT); 500 } 501 } 502 mCallCardFragment.animateForNewOutgoingCall(touchPoint); 503 504 /* 505 * If both a phone account handle and a list of phone accounts to choose from are 506 * missing, then disconnect the call because there is no way to place an outgoing 507 * call. 508 * The exception is emergency calls, which may be waiting for the ConnectionService 509 * to set the PhoneAccount during the PENDING_OUTGOING state. 510 */ 511 if (call != null && !isEmergencyCall(call)) { 512 final List<PhoneAccountHandle> phoneAccountHandles = extras 513 .getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); 514 if (call.getAccountHandle() == null && 515 (phoneAccountHandles == null || phoneAccountHandles.isEmpty())) { 516 TelecomAdapter.getInstance().disconnectCall(call.getId()); 517 } 518 } 519 } 520 521 Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall(); 522 if (pendingAccountSelectionCall != null) { 523 mCallCardFragment.setVisible(false); 524 Bundle extras = pendingAccountSelectionCall 525 .getTelecommCall().getDetails().getExtras(); 526 527 final List<PhoneAccountHandle> phoneAccountHandles; 528 if (extras != null) { 529 phoneAccountHandles = extras.getParcelableArrayList( 530 android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); 531 } else { 532 phoneAccountHandles = new ArrayList<>(); 533 } 534 535 SelectPhoneAccountDialogFragment.showAccountDialog(getFragmentManager(), 536 phoneAccountHandles); 537 } else { 538 mCallCardFragment.setVisible(true); 539 } 540 541 return; 542 } 543 } 544 545 private boolean isEmergencyCall(Call call) { 546 final Uri handle = call.getHandle(); 547 if (handle == null) { 548 return false; 549 } 550 return PhoneNumberUtils.isEmergencyNumber(handle.getSchemeSpecificPart()); 551 } 552 553 private void relaunchedFromDialer(boolean showDialpad) { 554 mShowDialpadRequested = showDialpad; 555 mAnimateDialpadOnShow = true; 556 557 if (mShowDialpadRequested) { 558 // If there's only one line in use, AND it's on hold, then we're sure the user 559 // wants to use the dialpad toward the exact line, so un-hold the holding line. 560 final Call call = CallList.getInstance().getActiveOrBackgroundCall(); 561 if (call != null && call.getState() == State.ONHOLD) { 562 TelecomAdapter.getInstance().unholdCall(call.getId()); 563 } 564 } 565 } 566 567 private void initializeInCall() { 568 if (mCallCardFragment == null) { 569 mCallCardFragment = (CallCardFragment) getFragmentManager() 570 .findFragmentById(R.id.callCardFragment); 571 } 572 573 mChildFragmentManager = mCallCardFragment.getChildFragmentManager(); 574 575 if (mCallButtonFragment == null) { 576 mCallButtonFragment = (CallButtonFragment) mChildFragmentManager 577 .findFragmentById(R.id.callButtonFragment); 578 mCallButtonFragment.getView().setVisibility(View.INVISIBLE); 579 } 580 581 if (mAnswerFragment == null) { 582 mAnswerFragment = (AnswerFragment) mChildFragmentManager 583 .findFragmentById(R.id.answerFragment); 584 } 585 586 if (mConferenceManagerFragment == null) { 587 mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager() 588 .findFragmentById(R.id.conferenceManagerFragment); 589 mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE); 590 } 591 } 592 593 /** 594 * Simulates a user click to hide the dialpad. This will update the UI to show the call card, 595 * update the checked state of the dialpad button, and update the proximity sensor state. 596 */ 597 public void hideDialpadForDisconnect() { 598 mCallButtonFragment.displayDialpad(false /* show */, true /* animate */); 599 } 600 601 public void dismissKeyguard(boolean dismiss) { 602 if (dismiss) { 603 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 604 } else { 605 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 606 } 607 } 608 609 private void showDialpad(boolean showDialpad) { 610 // If the dialpad is being shown and it has not already been loaded, replace the dialpad 611 // placeholder with the actual fragment before continuing. 612 if (mDialpadFragment == null && showDialpad) { 613 final FragmentTransaction loadTransaction = mChildFragmentManager.beginTransaction(); 614 View fragmentContainer = findViewById(R.id.dialpadFragmentContainer); 615 mDialpadFragment = new DialpadFragment(); 616 loadTransaction.replace(fragmentContainer.getId(), mDialpadFragment, 617 DialpadFragment.class.getName()); 618 loadTransaction.commitAllowingStateLoss(); 619 mChildFragmentManager.executePendingTransactions(); 620 } 621 622 final FragmentTransaction ft = mChildFragmentManager.beginTransaction(); 623 if (showDialpad) { 624 ft.show(mDialpadFragment); 625 } else { 626 ft.hide(mDialpadFragment); 627 } 628 ft.commitAllowingStateLoss(); 629 } 630 631 public void displayDialpad(boolean showDialpad, boolean animate) { 632 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out. 633 if ((showDialpad && isDialpadVisible()) || (!showDialpad && !isDialpadVisible())) { 634 return; 635 } 636 // We don't do a FragmentTransaction on the hide case because it will be dealt with when 637 // the listener is fired after an animation finishes. 638 if (!animate) { 639 showDialpad(showDialpad); 640 } else { 641 if (showDialpad) { 642 showDialpad(true); 643 mDialpadFragment.animateShowDialpad(); 644 } 645 mCallCardFragment.onDialpadVisiblityChange(showDialpad); 646 mDialpadFragment.getView().startAnimation(showDialpad ? mSlideIn : mSlideOut); 647 } 648 649 InCallPresenter.getInstance().getProximitySensor().onDialpadVisible(showDialpad); 650 } 651 652 public boolean isDialpadVisible() { 653 return mDialpadFragment != null && mDialpadFragment.isVisible(); 654 } 655 656 public void showConferenceCallManager() { 657 mConferenceManagerFragment.setVisible(true); 658 } 659 660 public void showPostCharWaitDialog(String callId, String chars) { 661 if (isForegroundActivity()) { 662 final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); 663 fragment.show(getFragmentManager(), "postCharWait"); 664 665 mShowPostCharWaitDialogOnResume = false; 666 mShowPostCharWaitDialogCallId = null; 667 mShowPostCharWaitDialogChars = null; 668 } else { 669 mShowPostCharWaitDialogOnResume = true; 670 mShowPostCharWaitDialogCallId = callId; 671 mShowPostCharWaitDialogChars = chars; 672 } 673 } 674 675 @Override 676 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 677 if (mCallCardFragment != null) { 678 mCallCardFragment.dispatchPopulateAccessibilityEvent(event); 679 } 680 return super.dispatchPopulateAccessibilityEvent(event); 681 } 682 683 public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) { 684 Log.d(this, "maybeShowErrorDialogOnDisconnect"); 685 686 if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription()) 687 && (disconnectCause.getCode() == DisconnectCause.ERROR || 688 disconnectCause.getCode() == DisconnectCause.RESTRICTED)) { 689 showErrorDialog(disconnectCause.getDescription()); 690 } 691 } 692 693 public void dismissPendingDialogs() { 694 if (mDialog != null) { 695 mDialog.dismiss(); 696 mDialog = null; 697 } 698 mAnswerFragment.dismissPendingDialogues(); 699 } 700 701 /** 702 * Utility function to bring up a generic "error" dialog. 703 */ 704 private void showErrorDialog(CharSequence msg) { 705 Log.i(this, "Show Dialog: " + msg); 706 707 dismissPendingDialogs(); 708 709 mDialog = new AlertDialog.Builder(this) 710 .setMessage(msg) 711 .setPositiveButton(R.string.ok, new OnClickListener() { 712 @Override 713 public void onClick(DialogInterface dialog, int which) { 714 onDialogDismissed(); 715 }}) 716 .setOnCancelListener(new OnCancelListener() { 717 @Override 718 public void onCancel(DialogInterface dialog) { 719 onDialogDismissed(); 720 }}) 721 .create(); 722 723 mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 724 mDialog.show(); 725 } 726 727 private void onDialogDismissed() { 728 mDialog = null; 729 InCallPresenter.getInstance().onDismissDialog(); 730 } 731 } 732