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