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