InCallActivity.java revision a62dc2266449adc677ddc7c8141e615f95e3652c
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 17package com.android.incallui; 18 19import android.app.ActionBar; 20import android.app.Activity; 21import android.app.ActivityManager; 22import android.app.AlertDialog; 23import android.app.DialogFragment; 24import android.app.Fragment; 25import android.app.FragmentManager; 26import android.app.FragmentTransaction; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.DialogInterface.OnCancelListener; 30import android.content.DialogInterface.OnClickListener; 31import android.content.Intent; 32import android.content.res.Configuration; 33import android.graphics.Point; 34import android.hardware.SensorManager; 35import android.os.Bundle; 36import android.os.Trace; 37import android.telecom.DisconnectCause; 38import android.telecom.PhoneAccountHandle; 39import android.text.TextUtils; 40import android.view.KeyEvent; 41import android.view.MenuItem; 42import android.view.MotionEvent; 43import android.view.OrientationEventListener; 44import android.view.Surface; 45import android.view.View; 46import android.view.View.OnTouchListener; 47import android.view.Window; 48import android.view.WindowManager; 49import android.view.accessibility.AccessibilityEvent; 50import android.view.animation.Animation; 51import android.view.animation.AnimationUtils; 52 53import com.android.contacts.common.activity.TransactionSafeActivity; 54import com.android.contacts.common.interactions.TouchPointManager; 55import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; 56import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; 57import com.android.dialer.logging.Logger; 58import com.android.dialer.logging.ScreenEvent; 59import com.android.incallui.Call.State; 60import com.android.incallui.util.AccessibilityUtil; 61import com.android.phone.common.animation.AnimUtils; 62import com.android.phone.common.animation.AnimationListenerAdapter; 63 64import java.util.ArrayList; 65import java.util.List; 66import java.util.Locale; 67 68/** 69 * Main activity that the user interacts with while in a live call. 70 */ 71public class InCallActivity extends TransactionSafeActivity implements FragmentDisplayManager { 72 73 public static final String TAG = InCallActivity.class.getSimpleName(); 74 75 public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad"; 76 public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text"; 77 public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call"; 78 79 private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment"; 80 private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment"; 81 private static final String TAG_CALLCARD_FRAGMENT = "tag_callcard_fragment"; 82 private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment"; 83 private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment"; 84 85 private CallButtonFragment mCallButtonFragment; 86 private CallCardFragment mCallCardFragment; 87 private AnswerFragment mAnswerFragment; 88 private DialpadFragment mDialpadFragment; 89 private ConferenceManagerFragment mConferenceManagerFragment; 90 private FragmentManager mChildFragmentManager; 91 92 private AlertDialog mDialog; 93 private InCallOrientationEventListener mInCallOrientationEventListener; 94 95 /** 96 * Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} 97 */ 98 private boolean mShowDialpadRequested; 99 100 /** 101 * Use to determine if the dialpad should be animated on show. 102 */ 103 private boolean mAnimateDialpadOnShow; 104 105 /** 106 * Use to determine the DTMF Text which should be pre-populated in the dialpad. 107 */ 108 private String mDtmfText; 109 110 /** 111 * Use to pass parameters for showing the PostCharDialog to {@link #onResume} 112 */ 113 private boolean mShowPostCharWaitDialogOnResume; 114 private String mShowPostCharWaitDialogCallId; 115 private String mShowPostCharWaitDialogChars; 116 117 private boolean mIsLandscape; 118 private Animation mSlideIn; 119 private Animation mSlideOut; 120 private boolean mDismissKeyguard = false; 121 122 AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() { 123 @Override 124 public void onAnimationEnd(Animation animation) { 125 showFragment(TAG_DIALPAD_FRAGMENT, false, true); 126 } 127 }; 128 129 private OnTouchListener mDispatchTouchEventListener; 130 131 private SelectPhoneAccountListener mSelectAcctListener = new SelectPhoneAccountListener() { 132 @Override 133 public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle, 134 boolean setDefault) { 135 InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle, 136 setDefault); 137 } 138 139 @Override 140 public void onDialogDismissed() { 141 InCallPresenter.getInstance().cancelAccountSelection(); 142 } 143 }; 144 145 @Override 146 protected void onCreate(Bundle icicle) { 147 Log.d(this, "onCreate()... this = " + this); 148 149 super.onCreate(icicle); 150 151 // set this flag so this activity will stay in front of the keyguard 152 // Have the WindowManager filter out touch events that are "too fat". 153 int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 154 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON 155 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 156 157 getWindow().addFlags(flags); 158 159 // Setup action bar for the conference call manager. 160 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 161 ActionBar actionBar = getActionBar(); 162 if (actionBar != null) { 163 actionBar.setDisplayHomeAsUpEnabled(true); 164 actionBar.setDisplayShowTitleEnabled(true); 165 actionBar.hide(); 166 } 167 168 // TODO(klp): Do we need to add this back when prox sensor is not available? 169 // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; 170 171 setContentView(R.layout.incall_screen); 172 173 internalResolveIntent(getIntent()); 174 175 mIsLandscape = getResources().getConfiguration().orientation == 176 Configuration.ORIENTATION_LANDSCAPE; 177 178 final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == 179 View.LAYOUT_DIRECTION_RTL; 180 181 if (mIsLandscape) { 182 mSlideIn = AnimationUtils.loadAnimation(this, 183 isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right); 184 mSlideOut = AnimationUtils.loadAnimation(this, 185 isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right); 186 } else { 187 mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom); 188 mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom); 189 } 190 191 mSlideIn.setInterpolator(AnimUtils.EASE_IN); 192 mSlideOut.setInterpolator(AnimUtils.EASE_OUT); 193 194 mSlideOut.setAnimationListener(mSlideOutListener); 195 196 if (icicle != null) { 197 // If the dialpad was shown before, set variables indicating it should be shown and 198 // populated with the previous DTMF text. The dialpad is actually shown and populated 199 // in onResume() to ensure the hosting CallCardFragment has been inflated and is ready 200 // to receive it. 201 mShowDialpadRequested = icicle.getBoolean(SHOW_DIALPAD_EXTRA); 202 mAnimateDialpadOnShow = false; 203 mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA); 204 205 SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment) 206 getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT); 207 if (dialogFragment != null) { 208 dialogFragment.setListener(mSelectAcctListener); 209 } 210 } 211 212 mInCallOrientationEventListener = new InCallOrientationEventListener(this); 213 214 Log.d(this, "onCreate(): exit"); 215 } 216 217 @Override 218 protected void onSaveInstanceState(Bundle out) { 219 // TODO: The dialpad fragment should handle this as part of its own state 220 out.putBoolean(SHOW_DIALPAD_EXTRA, 221 mCallButtonFragment != null && mCallButtonFragment.isDialpadVisible()); 222 if (mDialpadFragment != null) { 223 out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText()); 224 } 225 super.onSaveInstanceState(out); 226 } 227 228 @Override 229 protected void onStart() { 230 Log.d(this, "onStart()..."); 231 super.onStart(); 232 233 // setting activity should be last thing in setup process 234 InCallPresenter.getInstance().setActivity(this); 235 enableInCallOrientationEventListener(getRequestedOrientation() == 236 InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION); 237 238 InCallPresenter.getInstance().onActivityStarted(); 239 } 240 241 @Override 242 protected void onResume() { 243 Log.i(this, "onResume()..."); 244 super.onResume(); 245 246 InCallPresenter.getInstance().setThemeColors(); 247 InCallPresenter.getInstance().onUiShowing(true); 248 249 if (mShowDialpadRequested) { 250 mCallButtonFragment.displayDialpad(true /* show */, 251 mAnimateDialpadOnShow /* animate */); 252 mShowDialpadRequested = false; 253 mAnimateDialpadOnShow = false; 254 255 if (mDialpadFragment != null) { 256 mDialpadFragment.setDtmfText(mDtmfText); 257 mDtmfText = null; 258 } 259 } 260 261 if (mShowPostCharWaitDialogOnResume) { 262 showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars); 263 } 264 } 265 266 // onPause is guaranteed to be called when the InCallActivity goes 267 // in the background. 268 @Override 269 protected void onPause() { 270 Log.d(this, "onPause()..."); 271 if (mDialpadFragment != null) { 272 mDialpadFragment.onDialerKeyUp(null); 273 } 274 275 InCallPresenter.getInstance().onUiShowing(false); 276 if (isFinishing()) { 277 InCallPresenter.getInstance().unsetActivity(this); 278 } 279 super.onPause(); 280 } 281 282 @Override 283 protected void onStop() { 284 Log.d(this, "onStop()..."); 285 enableInCallOrientationEventListener(false); 286 InCallPresenter.getInstance().updateIsChangingConfigurations(); 287 InCallPresenter.getInstance().onActivityStopped(); 288 super.onStop(); 289 } 290 291 @Override 292 protected void onDestroy() { 293 Log.d(this, "onDestroy()... this = " + this); 294 InCallPresenter.getInstance().unsetActivity(this); 295 InCallPresenter.getInstance().updateIsChangingConfigurations(); 296 super.onDestroy(); 297 } 298 299 /** 300 * When fragments have a parent fragment, onAttachFragment is not called on the parent 301 * activity. To fix this, register our own callback instead that is always called for 302 * all fragments. 303 * 304 * @see {@link BaseFragment#onAttach(Activity)} 305 */ 306 @Override 307 public void onFragmentAttached(Fragment fragment) { 308 if (fragment instanceof DialpadFragment) { 309 mDialpadFragment = (DialpadFragment) fragment; 310 } else if (fragment instanceof AnswerFragment) { 311 mAnswerFragment = (AnswerFragment) fragment; 312 } else if (fragment instanceof CallCardFragment) { 313 mCallCardFragment = (CallCardFragment) fragment; 314 mChildFragmentManager = mCallCardFragment.getChildFragmentManager(); 315 } else if (fragment instanceof ConferenceManagerFragment) { 316 mConferenceManagerFragment = (ConferenceManagerFragment) fragment; 317 } else if (fragment instanceof CallButtonFragment) { 318 mCallButtonFragment = (CallButtonFragment) fragment; 319 } 320 } 321 322 /** 323 * Returns true when the Activity is currently visible. 324 */ 325 /* package */ boolean isVisible() { 326 return isSafeToCommitTransactions(); 327 } 328 329 private boolean hasPendingDialogs() { 330 return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs()); 331 } 332 333 @Override 334 public void finish() { 335 Log.i(this, "finish(). Dialog showing: " + (mDialog != null)); 336 337 // skip finish if we are still showing a dialog. 338 if (!hasPendingDialogs()) { 339 super.finish(); 340 } 341 } 342 343 @Override 344 protected void onNewIntent(Intent intent) { 345 Log.d(this, "onNewIntent: intent = " + intent); 346 347 // We're being re-launched with a new Intent. Since it's possible for a 348 // single InCallActivity instance to persist indefinitely (even if we 349 // finish() ourselves), this sequence can potentially happen any time 350 // the InCallActivity needs to be displayed. 351 352 // Stash away the new intent so that we can get it in the future 353 // by calling getIntent(). (Otherwise getIntent() will return the 354 // original Intent from when we first got created!) 355 setIntent(intent); 356 357 // Activities are always paused before receiving a new intent, so 358 // we can count on our onResume() method being called next. 359 360 // Just like in onCreate(), handle the intent. 361 internalResolveIntent(intent); 362 } 363 364 @Override 365 public void onBackPressed() { 366 Log.i(this, "onBackPressed"); 367 368 // BACK is also used to exit out of any "special modes" of the 369 // in-call UI: 370 if (!isVisible()) { 371 return; 372 } 373 374 if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible()) 375 && (mCallCardFragment == null || !mCallCardFragment.isVisible())) { 376 return; 377 } 378 379 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 380 mCallButtonFragment.displayDialpad(false /* show */, true /* animate */); 381 return; 382 } else if (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible()) { 383 showConferenceFragment(false); 384 return; 385 } 386 387 // Always disable the Back key while an incoming call is ringing 388 final Call call = CallList.getInstance().getIncomingCall(); 389 if (call != null) { 390 Log.i(this, "Consume Back press for an incoming call"); 391 return; 392 } 393 394 // Nothing special to do. Fall back to the default behavior. 395 super.onBackPressed(); 396 } 397 398 @Override 399 public boolean onOptionsItemSelected(MenuItem item) { 400 final int itemId = item.getItemId(); 401 if (itemId == android.R.id.home) { 402 onBackPressed(); 403 return true; 404 } 405 return super.onOptionsItemSelected(item); 406 } 407 408 @Override 409 public boolean onKeyUp(int keyCode, KeyEvent event) { 410 // push input to the dialer. 411 if (mDialpadFragment != null && (mDialpadFragment.isVisible()) && 412 (mDialpadFragment.onDialerKeyUp(event))) { 413 return true; 414 } else if (keyCode == KeyEvent.KEYCODE_CALL) { 415 // Always consume CALL to be sure the PhoneWindow won't do anything with it 416 return true; 417 } 418 return super.onKeyUp(keyCode, event); 419 } 420 421 @Override 422 public boolean dispatchTouchEvent(MotionEvent ev) { 423 if (mDispatchTouchEventListener != null) { 424 boolean handled = mDispatchTouchEventListener.onTouch(null, ev); 425 if (handled) { 426 return true; 427 } 428 } 429 return super.dispatchTouchEvent(ev); 430 } 431 432 @Override 433 public boolean onKeyDown(int keyCode, KeyEvent event) { 434 switch (keyCode) { 435 case KeyEvent.KEYCODE_CALL: 436 boolean handled = InCallPresenter.getInstance().handleCallKey(); 437 if (!handled) { 438 Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown"); 439 } 440 // Always consume CALL to be sure the PhoneWindow won't do anything with it 441 return true; 442 443 // Note there's no KeyEvent.KEYCODE_ENDCALL case here. 444 // The standard system-wide handling of the ENDCALL key 445 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL) 446 // already implements exactly what the UI spec wants, 447 // namely (1) "hang up" if there's a current active call, 448 // or (2) "don't answer" if there's a current ringing call. 449 450 case KeyEvent.KEYCODE_CAMERA: 451 // Disable the CAMERA button while in-call since it's too 452 // easy to press accidentally. 453 return true; 454 455 case KeyEvent.KEYCODE_VOLUME_UP: 456 case KeyEvent.KEYCODE_VOLUME_DOWN: 457 case KeyEvent.KEYCODE_VOLUME_MUTE: 458 // Ringer silencing handled by PhoneWindowManager. 459 break; 460 461 case KeyEvent.KEYCODE_MUTE: 462 // toggle mute 463 TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute()); 464 return true; 465 466 // Various testing/debugging features, enabled ONLY when VERBOSE == true. 467 case KeyEvent.KEYCODE_SLASH: 468 if (Log.VERBOSE) { 469 Log.v(this, "----------- InCallActivity View dump --------------"); 470 // Dump starting from the top-level view of the entire activity: 471 Window w = this.getWindow(); 472 View decorView = w.getDecorView(); 473 Log.d(this, "View dump:" + decorView); 474 return true; 475 } 476 break; 477 case KeyEvent.KEYCODE_EQUALS: 478 // TODO: Dump phone state? 479 break; 480 } 481 482 if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) { 483 return true; 484 } 485 return super.onKeyDown(keyCode, event); 486 } 487 488 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) { 489 Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "..."); 490 491 // As soon as the user starts typing valid dialable keys on the 492 // keyboard (presumably to type DTMF tones) we start passing the 493 // key events to the DTMFDialer's onDialerKeyDown. 494 if (mDialpadFragment != null && mDialpadFragment.isVisible()) { 495 return mDialpadFragment.onDialerKeyDown(event); 496 } 497 498 return false; 499 } 500 501 public CallButtonFragment getCallButtonFragment() { 502 return mCallButtonFragment; 503 } 504 505 public CallCardFragment getCallCardFragment() { 506 return mCallCardFragment; 507 } 508 509 public AnswerFragment getAnswerFragment() { 510 return mAnswerFragment; 511 } 512 513 private void internalResolveIntent(Intent intent) { 514 final String action = intent.getAction(); 515 if (action.equals(Intent.ACTION_MAIN)) { 516 // This action is the normal way to bring up the in-call UI. 517 // 518 // But we do check here for one extra that can come along with the 519 // ACTION_MAIN intent: 520 521 if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) { 522 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF 523 // dialpad should be initially visible. If the extra isn't 524 // present at all, we just leave the dialpad in its previous state. 525 526 final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false); 527 Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad); 528 529 relaunchedFromDialer(showDialpad); 530 } 531 532 boolean newOutgoingCall = false; 533 if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) { 534 intent.removeExtra(NEW_OUTGOING_CALL_EXTRA); 535 Call call = CallList.getInstance().getOutgoingCall(); 536 if (call == null) { 537 call = CallList.getInstance().getPendingOutgoingCall(); 538 } 539 540 Bundle extras = null; 541 if (call != null) { 542 extras = call.getTelecomCall().getDetails().getIntentExtras(); 543 } 544 if (extras == null) { 545 // Initialize the extras bundle to avoid NPE 546 extras = new Bundle(); 547 } 548 549 Point touchPoint = null; 550 if (TouchPointManager.getInstance().hasValidPoint()) { 551 // Use the most immediate touch point in the InCallUi if available 552 touchPoint = TouchPointManager.getInstance().getPoint(); 553 } else { 554 // Otherwise retrieve the touch point from the call intent 555 if (call != null) { 556 touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT); 557 } 558 } 559 560 // Start animation for new outgoing call 561 CircularRevealFragment.startCircularReveal(getFragmentManager(), touchPoint, 562 InCallPresenter.getInstance()); 563 564 // InCallActivity is responsible for disconnecting a new outgoing call if there 565 // is no way of making it (i.e. no valid call capable accounts) 566 if (InCallPresenter.isCallWithNoValidAccounts(call)) { 567 TelecomAdapter.getInstance().disconnectCall(call.getId()); 568 } 569 570 dismissKeyguard(true); 571 newOutgoingCall = true; 572 } 573 574 Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall(); 575 if (pendingAccountSelectionCall != null) { 576 showCallCardFragment(false); 577 Bundle extras = pendingAccountSelectionCall 578 .getTelecomCall().getDetails().getIntentExtras(); 579 580 final List<PhoneAccountHandle> phoneAccountHandles; 581 if (extras != null) { 582 phoneAccountHandles = extras.getParcelableArrayList( 583 android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); 584 } else { 585 phoneAccountHandles = new ArrayList<>(); 586 } 587 588 DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance( 589 R.string.select_phone_account_for_calls, true, phoneAccountHandles, 590 mSelectAcctListener); 591 dialogFragment.show(getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT); 592 } else if (!newOutgoingCall) { 593 showCallCardFragment(true); 594 } 595 596 return; 597 } 598 } 599 600 private void relaunchedFromDialer(boolean showDialpad) { 601 mShowDialpadRequested = showDialpad; 602 mAnimateDialpadOnShow = true; 603 604 if (mShowDialpadRequested) { 605 // If there's only one line in use, AND it's on hold, then we're sure the user 606 // wants to use the dialpad toward the exact line, so un-hold the holding line. 607 final Call call = CallList.getInstance().getActiveOrBackgroundCall(); 608 if (call != null && call.getState() == State.ONHOLD) { 609 TelecomAdapter.getInstance().unholdCall(call.getId()); 610 } 611 } 612 } 613 614 public void dismissKeyguard(boolean dismiss) { 615 if (mDismissKeyguard == dismiss) { 616 return; 617 } 618 mDismissKeyguard = dismiss; 619 if (dismiss) { 620 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 621 } else { 622 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 623 } 624 } 625 626 private void showFragment(String tag, boolean show, boolean executeImmediately) { 627 Trace.beginSection("showFragment - " + tag); 628 final FragmentManager fm = getFragmentManagerForTag(tag); 629 630 if (fm == null) { 631 Log.w(TAG, "Fragment manager is null for : " + tag); 632 return; 633 } 634 635 Fragment fragment = fm.findFragmentByTag(tag); 636 if (!show && fragment == null) { 637 // Nothing to show, so bail early. 638 return; 639 } 640 641 final FragmentTransaction transaction = fm.beginTransaction(); 642 if (show) { 643 if (fragment == null) { 644 fragment = createNewFragmentForTag(tag); 645 transaction.add(getContainerIdForFragment(tag), fragment, tag); 646 } else { 647 transaction.show(fragment); 648 } 649 Logger.logScreenView(getScreenTypeForTag(tag), this); 650 } else { 651 transaction.hide(fragment); 652 } 653 654 transaction.commitAllowingStateLoss(); 655 if (executeImmediately) { 656 fm.executePendingTransactions(); 657 } 658 Trace.endSection(); 659 } 660 661 private Fragment createNewFragmentForTag(String tag) { 662 if (TAG_DIALPAD_FRAGMENT.equals(tag)) { 663 mDialpadFragment = new DialpadFragment(); 664 return mDialpadFragment; 665 } else if (TAG_ANSWER_FRAGMENT.equals(tag)) { 666 if (AccessibilityUtil.isTalkBackEnabled(this)) { 667 mAnswerFragment = new AccessibleAnswerFragment(); 668 } else { 669 mAnswerFragment = new GlowPadAnswerFragment(); 670 } 671 return mAnswerFragment; 672 } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) { 673 mConferenceManagerFragment = new ConferenceManagerFragment(); 674 return mConferenceManagerFragment; 675 } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) { 676 mCallCardFragment = new CallCardFragment(); 677 return mCallCardFragment; 678 } 679 throw new IllegalStateException("Unexpected fragment: " + tag); 680 } 681 682 private FragmentManager getFragmentManagerForTag(String tag) { 683 if (TAG_DIALPAD_FRAGMENT.equals(tag)) { 684 return mChildFragmentManager; 685 } else if (TAG_ANSWER_FRAGMENT.equals(tag)) { 686 return mChildFragmentManager; 687 } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) { 688 return getFragmentManager(); 689 } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) { 690 return getFragmentManager(); 691 } 692 throw new IllegalStateException("Unexpected fragment: " + tag); 693 } 694 695 private int getScreenTypeForTag(String tag) { 696 switch (tag) { 697 case TAG_DIALPAD_FRAGMENT: 698 return ScreenEvent.INCALL_DIALPAD; 699 case TAG_CALLCARD_FRAGMENT: 700 return ScreenEvent.INCALL; 701 case TAG_CONFERENCE_FRAGMENT: 702 return ScreenEvent.CONFERENCE_MANAGEMENT; 703 case TAG_ANSWER_FRAGMENT: 704 return ScreenEvent.INCOMING_CALL; 705 default: 706 return ScreenEvent.UNKNOWN; 707 } 708 } 709 710 private int getContainerIdForFragment(String tag) { 711 if (TAG_DIALPAD_FRAGMENT.equals(tag)) { 712 return R.id.answer_and_dialpad_container; 713 } else if (TAG_ANSWER_FRAGMENT.equals(tag)) { 714 return R.id.answer_and_dialpad_container; 715 } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) { 716 return R.id.main; 717 } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) { 718 return R.id.main; 719 } 720 throw new IllegalStateException("Unexpected fragment: " + tag); 721 } 722 723 /** 724 * @return {@code true} while the visibility of the dialpad has actually changed. 725 */ 726 public boolean showDialpadFragment(boolean show, boolean animate) { 727 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out. 728 if ((show && isDialpadVisible()) || (!show && !isDialpadVisible())) { 729 return false; 730 } 731 // We don't do a FragmentTransaction on the hide case because it will be dealt with when 732 // the listener is fired after an animation finishes. 733 if (!animate) { 734 showFragment(TAG_DIALPAD_FRAGMENT, show, true); 735 } else { 736 if (show) { 737 showFragment(TAG_DIALPAD_FRAGMENT, true, true); 738 mDialpadFragment.animateShowDialpad(); 739 } 740 mCallCardFragment.onDialpadVisibilityChange(show); 741 mDialpadFragment.getView().startAnimation(show ? mSlideIn : mSlideOut); 742 } 743 744 final ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor(); 745 if (sensor != null) { 746 sensor.onDialpadVisible(show); 747 } 748 return true; 749 } 750 751 public boolean isDialpadVisible() { 752 return mDialpadFragment != null && mDialpadFragment.isVisible(); 753 } 754 755 public void showCallCardFragment(boolean show) { 756 showFragment(TAG_CALLCARD_FRAGMENT, show, true); 757 } 758 759 /** 760 * Hides or shows the conference manager fragment. 761 * 762 * @param show {@code true} if the conference manager should be shown, {@code false} if it 763 * should be hidden. 764 */ 765 public void showConferenceFragment(boolean show) { 766 showFragment(TAG_CONFERENCE_FRAGMENT, show, true); 767 mConferenceManagerFragment.onVisibilityChanged(show); 768 769 // Need to hide the call card fragment to ensure that accessibility service does not try to 770 // give focus to the call card when the conference manager is visible. 771 mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE); 772 } 773 774 public void showAnswerFragment(boolean show) { 775 showFragment(TAG_ANSWER_FRAGMENT, show, true); 776 } 777 778 public void showPostCharWaitDialog(String callId, String chars) { 779 if (isVisible()) { 780 final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); 781 fragment.show(getFragmentManager(), "postCharWait"); 782 783 mShowPostCharWaitDialogOnResume = false; 784 mShowPostCharWaitDialogCallId = null; 785 mShowPostCharWaitDialogChars = null; 786 } else { 787 mShowPostCharWaitDialogOnResume = true; 788 mShowPostCharWaitDialogCallId = callId; 789 mShowPostCharWaitDialogChars = chars; 790 } 791 } 792 793 @Override 794 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 795 if (mCallCardFragment != null) { 796 mCallCardFragment.dispatchPopulateAccessibilityEvent(event); 797 } 798 return super.dispatchPopulateAccessibilityEvent(event); 799 } 800 801 public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) { 802 Log.d(this, "maybeShowErrorDialogOnDisconnect"); 803 804 if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription()) 805 && (disconnectCause.getCode() == DisconnectCause.ERROR || 806 disconnectCause.getCode() == DisconnectCause.RESTRICTED)) { 807 showErrorDialog(disconnectCause.getDescription()); 808 } 809 } 810 811 public void dismissPendingDialogs() { 812 if (mDialog != null) { 813 mDialog.dismiss(); 814 mDialog = null; 815 } 816 if (mAnswerFragment != null) { 817 mAnswerFragment.dismissPendingDialogs(); 818 } 819 } 820 821 /** 822 * Utility function to bring up a generic "error" dialog. 823 */ 824 private void showErrorDialog(CharSequence msg) { 825 Log.i(this, "Show Dialog: " + msg); 826 827 dismissPendingDialogs(); 828 829 mDialog = new AlertDialog.Builder(this) 830 .setMessage(msg) 831 .setPositiveButton(android.R.string.ok, new OnClickListener() { 832 @Override 833 public void onClick(DialogInterface dialog, int which) { 834 onDialogDismissed(); 835 } 836 }) 837 .setOnCancelListener(new OnCancelListener() { 838 @Override 839 public void onCancel(DialogInterface dialog) { 840 onDialogDismissed(); 841 } 842 }) 843 .create(); 844 845 mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 846 mDialog.show(); 847 } 848 849 private void onDialogDismissed() { 850 mDialog = null; 851 CallList.getInstance().onErrorDialogDismissed(); 852 InCallPresenter.getInstance().onDismissDialog(); 853 } 854 855 public void setExcludeFromRecents(boolean exclude) { 856 ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 857 List<ActivityManager.AppTask> tasks = am.getAppTasks(); 858 int taskId = getTaskId(); 859 for (int i = 0; i < tasks.size(); i++) { 860 ActivityManager.AppTask task = tasks.get(i); 861 if (task.getTaskInfo().id == taskId) { 862 try { 863 task.setExcludeFromRecents(exclude); 864 } catch (RuntimeException e) { 865 Log.e(TAG, "RuntimeException when excluding task from recents.", e); 866 } 867 } 868 } 869 } 870 871 872 public OnTouchListener getDispatchTouchEventListener() { 873 return mDispatchTouchEventListener; 874 } 875 876 public void setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener) { 877 this.mDispatchTouchEventListener = mDispatchTouchEventListener; 878 } 879 880 /** 881 * Enables the OrientationEventListener if enable flag is true. Disables it if enable is 882 * false 883 * @param enable true or false. 884 */ 885 public void enableInCallOrientationEventListener(boolean enable) { 886 if (enable) { 887 mInCallOrientationEventListener.enable(enable); 888 } else { 889 mInCallOrientationEventListener.disable(); 890 } 891 } 892} 893