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