1 /* 2 * Copyright (C) 2016 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.ActivityManager; 20 import android.app.ActivityManager.AppTask; 21 import android.app.ActivityManager.TaskDescription; 22 import android.app.AlertDialog; 23 import android.app.Dialog; 24 import android.app.KeyguardManager; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.res.Configuration; 28 import android.graphics.drawable.GradientDrawable; 29 import android.graphics.drawable.GradientDrawable.Orientation; 30 import android.os.Bundle; 31 import android.os.Trace; 32 import android.support.annotation.ColorInt; 33 import android.support.annotation.FloatRange; 34 import android.support.annotation.IntDef; 35 import android.support.annotation.NonNull; 36 import android.support.annotation.Nullable; 37 import android.support.annotation.VisibleForTesting; 38 import android.support.v4.app.FragmentManager; 39 import android.support.v4.app.FragmentTransaction; 40 import android.support.v4.content.res.ResourcesCompat; 41 import android.support.v4.graphics.ColorUtils; 42 import android.telecom.CallAudioState; 43 import android.telecom.PhoneAccountHandle; 44 import android.telephony.TelephonyManager; 45 import android.view.KeyEvent; 46 import android.view.MenuItem; 47 import android.view.MotionEvent; 48 import android.view.View; 49 import android.view.WindowManager; 50 import android.view.animation.Animation; 51 import android.view.animation.AnimationUtils; 52 import android.widget.CheckBox; 53 import android.widget.Toast; 54 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; 55 import com.android.dialer.animation.AnimUtils; 56 import com.android.dialer.animation.AnimationListenerAdapter; 57 import com.android.dialer.common.Assert; 58 import com.android.dialer.common.LogUtil; 59 import com.android.dialer.common.concurrent.ThreadUtil; 60 import com.android.dialer.compat.ActivityCompat; 61 import com.android.dialer.compat.CompatUtils; 62 import com.android.dialer.configprovider.ConfigProviderBindings; 63 import com.android.dialer.logging.Logger; 64 import com.android.dialer.logging.ScreenEvent; 65 import com.android.dialer.metrics.Metrics; 66 import com.android.dialer.metrics.MetricsComponent; 67 import com.android.dialer.util.ViewUtil; 68 import com.android.incallui.answer.bindings.AnswerBindings; 69 import com.android.incallui.answer.protocol.AnswerScreen; 70 import com.android.incallui.answer.protocol.AnswerScreenDelegate; 71 import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory; 72 import com.android.incallui.answerproximitysensor.PseudoScreenState; 73 import com.android.incallui.audiomode.AudioModeProvider; 74 import com.android.incallui.call.CallList; 75 import com.android.incallui.call.DialerCall; 76 import com.android.incallui.call.DialerCall.State; 77 import com.android.incallui.call.TelecomAdapter; 78 import com.android.incallui.callpending.CallPendingActivity; 79 import com.android.incallui.disconnectdialog.DisconnectMessage; 80 import com.android.incallui.incall.bindings.InCallBindings; 81 import com.android.incallui.incall.protocol.InCallButtonUiDelegate; 82 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; 83 import com.android.incallui.incall.protocol.InCallScreen; 84 import com.android.incallui.incall.protocol.InCallScreenDelegate; 85 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; 86 import com.android.incallui.incalluilock.InCallUiLock; 87 import com.android.incallui.rtt.bindings.RttBindings; 88 import com.android.incallui.rtt.protocol.RttCallScreen; 89 import com.android.incallui.rtt.protocol.RttCallScreenDelegate; 90 import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory; 91 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment; 92 import com.android.incallui.video.bindings.VideoBindings; 93 import com.android.incallui.video.protocol.VideoCallScreen; 94 import com.android.incallui.video.protocol.VideoCallScreenDelegate; 95 import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory; 96 import com.google.common.base.Optional; 97 import java.lang.annotation.Retention; 98 import java.lang.annotation.RetentionPolicy; 99 import java.util.ArrayList; 100 import java.util.List; 101 102 /** Version of {@link InCallActivity} that shows the new UI */ 103 public class InCallActivity extends TransactionSafeFragmentActivity 104 implements AnswerScreenDelegateFactory, 105 InCallScreenDelegateFactory, 106 InCallButtonUiDelegateFactory, 107 VideoCallScreenDelegateFactory, 108 RttCallScreenDelegateFactory, 109 PseudoScreenState.StateChangedListener { 110 111 @Retention(RetentionPolicy.SOURCE) 112 @IntDef({ 113 DIALPAD_REQUEST_NONE, 114 DIALPAD_REQUEST_SHOW, 115 DIALPAD_REQUEST_HIDE, 116 }) 117 @interface DialpadRequestType {} 118 119 private static final int DIALPAD_REQUEST_NONE = 1; 120 private static final int DIALPAD_REQUEST_SHOW = 2; 121 private static final int DIALPAD_REQUEST_HIDE = 3; 122 123 private static Optional<Integer> audioRouteForTesting = Optional.absent(); 124 125 private final InternationalCallOnWifiCallback internationalCallOnWifiCallback = 126 new InternationalCallOnWifiCallback(); 127 private final SelectPhoneAccountListener selectPhoneAccountListener = 128 new SelectPhoneAccountListener(); 129 130 private Animation dialpadSlideInAnimation; 131 private Animation dialpadSlideOutAnimation; 132 private Dialog errorDialog; 133 private GradientDrawable backgroundDrawable; 134 private InCallOrientationEventListener inCallOrientationEventListener; 135 private View pseudoBlackScreenOverlay; 136 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment; 137 private String dtmfTextToPrepopulate; 138 private String showPostCharWaitDialogCallId; 139 private String showPostCharWaitDialogChars; 140 private boolean allowOrientationChange; 141 private boolean animateDialpadOnShow; 142 private boolean didShowAnswerScreen; 143 private boolean didShowInCallScreen; 144 private boolean didShowVideoCallScreen; 145 private boolean didShowRttCallScreen; 146 private boolean dismissKeyguard; 147 private boolean isInShowMainInCallFragment; 148 private boolean isRecreating; // whether the activity is going to be recreated 149 private boolean isVisible; 150 private boolean needDismissPendingDialogs; 151 private boolean showPostCharWaitDialogOnResume; 152 private boolean touchDownWhenPseudoScreenOff; 153 private int[] backgroundDrawableColors; 154 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE; 155 156 public static Intent getIntent( 157 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) { 158 Intent intent = new Intent(Intent.ACTION_MAIN, null); 159 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); 160 intent.setClass(context, InCallActivity.class); 161 if (showDialpad) { 162 intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true); 163 } 164 intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall); 165 intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen); 166 return intent; 167 } 168 169 @Override 170 protected void onResumeFragments() { 171 super.onResumeFragments(); 172 if (needDismissPendingDialogs) { 173 dismissPendingDialogs(); 174 } 175 } 176 177 @Override 178 protected void onCreate(Bundle bundle) { 179 Trace.beginSection("InCallActivity.onCreate"); 180 super.onCreate(bundle); 181 182 if (bundle != null) { 183 didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN); 184 didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN); 185 didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN); 186 didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN); 187 } 188 189 setWindowFlags(); 190 setContentView(R.layout.incall_screen); 191 internalResolveIntent(getIntent()); 192 193 boolean isLandscape = 194 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; 195 boolean isRtl = ViewUtil.isRtl(); 196 if (isLandscape) { 197 dialpadSlideInAnimation = 198 AnimationUtils.loadAnimation( 199 this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right); 200 dialpadSlideOutAnimation = 201 AnimationUtils.loadAnimation( 202 this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right); 203 } else { 204 dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom); 205 dialpadSlideOutAnimation = 206 AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom); 207 } 208 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN); 209 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT); 210 dialpadSlideOutAnimation.setAnimationListener( 211 new AnimationListenerAdapter() { 212 @Override 213 public void onAnimationEnd(Animation animation) { 214 hideDialpadFragment(); 215 } 216 }); 217 218 if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) { 219 // If the dialpad was shown before, set related variables so that it can be shown and 220 // populated with the previous DTMF text during onResume(). 221 if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) { 222 boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD); 223 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE; 224 animateDialpadOnShow = false; 225 } 226 dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT); 227 228 SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment = 229 (SelectPhoneAccountDialogFragment) 230 getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT); 231 if (selectPhoneAccountDialogFragment != null) { 232 selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener); 233 } 234 } 235 236 InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment = 237 (InternationalCallOnWifiDialogFragment) 238 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI); 239 if (existingInternationalCallOnWifiDialogFragment != null) { 240 existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback); 241 } 242 243 inCallOrientationEventListener = new InCallOrientationEventListener(this); 244 245 getWindow() 246 .getDecorView() 247 .setSystemUiVisibility( 248 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 249 250 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay); 251 sendBroadcast(CallPendingActivity.getFinishBroadcast()); 252 Trace.endSection(); 253 MetricsComponent.get(this) 254 .metrics() 255 .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING); 256 MetricsComponent.get(this) 257 .metrics() 258 .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING); 259 } 260 261 private void setWindowFlags() { 262 // Allow the activity to be shown when the screen is locked and filter out touch events that are 263 // "too fat". 264 int flags = 265 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 266 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 267 268 // When the audio stream is not via Bluetooth, turn on the screen once the activity is shown. 269 // When the audio stream is via Bluetooth, turn on the screen only for an incoming call. 270 final int audioRoute = getAudioRoute(); 271 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH 272 || CallList.getInstance().getIncomingCall() != null) { 273 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; 274 } 275 276 getWindow().addFlags(flags); 277 } 278 279 private static int getAudioRoute() { 280 if (audioRouteForTesting.isPresent()) { 281 return audioRouteForTesting.get(); 282 } 283 284 return AudioModeProvider.getInstance().getAudioState().getRoute(); 285 } 286 287 @VisibleForTesting(otherwise = VisibleForTesting.NONE) 288 public static void setAudioRouteForTesting(int audioRoute) { 289 audioRouteForTesting = Optional.of(audioRoute); 290 } 291 292 private void internalResolveIntent(Intent intent) { 293 if (!intent.getAction().equals(Intent.ACTION_MAIN)) { 294 return; 295 } 296 297 if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) { 298 // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be 299 // initially visible. If the extra is absent, leave the dialpad in its previous state. 300 boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false); 301 relaunchedFromDialer(showDialpad); 302 } 303 304 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall(); 305 if (outgoingCall == null) { 306 outgoingCall = CallList.getInstance().getPendingOutgoingCall(); 307 } 308 if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) { 309 intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL); 310 311 // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of 312 // making it (i.e. no valid call capable accounts). 313 // If the version is not MSIM compatible, ignore this code. 314 if (CompatUtils.isMSIMCompatible() 315 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) { 316 LogUtil.i( 317 "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting"); 318 outgoingCall.disconnect(); 319 } 320 321 dismissKeyguard(true); 322 } 323 324 if (showPhoneAccountSelectionDialog()) { 325 hideMainInCallFragment(); 326 } 327 } 328 329 /** 330 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should 331 * be shown on launch. 332 * 333 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code 334 * false} to indicate no change should be made to the dialpad visibility. 335 */ 336 private void relaunchedFromDialer(boolean showDialpad) { 337 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE; 338 animateDialpadOnShow = true; 339 340 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) { 341 // If there's only one line in use, AND it's on hold, then we're sure the user 342 // wants to use the dialpad toward the exact line, so un-hold the holding line. 343 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall(); 344 if (call != null && call.getState() == State.ONHOLD) { 345 call.unhold(); 346 } 347 } 348 } 349 350 /** 351 * Show a phone account selection dialog if there is a call waiting for phone account selection. 352 * 353 * @return true if the dialog was shown. 354 */ 355 private boolean showPhoneAccountSelectionDialog() { 356 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall(); 357 if (waitingForAccountCall == null) { 358 return false; 359 } 360 361 Bundle extras = waitingForAccountCall.getIntentExtras(); 362 List<PhoneAccountHandle> phoneAccountHandles = 363 extras == null 364 ? new ArrayList<>() 365 : extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS); 366 367 selectPhoneAccountDialogFragment = 368 SelectPhoneAccountDialogFragment.newInstance( 369 R.string.select_phone_account_for_calls, 370 true /* canSetDefault */, 371 0 /* setDefaultResId */, 372 phoneAccountHandles, 373 selectPhoneAccountListener, 374 waitingForAccountCall.getId(), 375 null /* hints */); 376 selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT); 377 return true; 378 } 379 380 @Override 381 protected void onSaveInstanceState(Bundle out) { 382 LogUtil.enterBlock("InCallActivity.onSaveInstanceState"); 383 384 // TODO: DialpadFragment should handle this as part of its own state 385 out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible()); 386 DialpadFragment dialpadFragment = getDialpadFragment(); 387 if (dialpadFragment != null) { 388 out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText()); 389 } 390 391 out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen); 392 out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen); 393 out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen); 394 out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen); 395 396 super.onSaveInstanceState(out); 397 isVisible = false; 398 } 399 400 @Override 401 protected void onStart() { 402 Trace.beginSection("InCallActivity.onStart"); 403 super.onStart(); 404 405 isVisible = true; 406 showMainInCallFragment(); 407 408 InCallPresenter.getInstance().setActivity(this); 409 enableInCallOrientationEventListener( 410 getRequestedOrientation() 411 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION); 412 InCallPresenter.getInstance().onActivityStarted(); 413 414 if (!isRecreating) { 415 InCallPresenter.getInstance().onUiShowing(true); 416 } 417 418 if (ActivityCompat.isInMultiWindowMode(this) 419 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) { 420 // Hide the dialpad because there may not be enough room 421 showDialpadFragment(false, false); 422 } 423 424 Trace.endSection(); 425 } 426 427 @Override 428 protected void onResume() { 429 Trace.beginSection("InCallActivity.onResume"); 430 super.onResume(); 431 432 if (!InCallPresenter.getInstance().isReadyForTearDown()) { 433 updateTaskDescription(); 434 InCallPresenter.getInstance().updateNotification(); 435 } 436 437 // If there is a pending request to show or hide the dialpad, handle that now. 438 if (showDialpadRequest != DIALPAD_REQUEST_NONE) { 439 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) { 440 // Exit fullscreen so that the user has access to the dialpad hide/show button. 441 // This is important when showing the dialpad from within dialer. 442 InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */); 443 444 showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */); 445 animateDialpadOnShow = false; 446 447 DialpadFragment dialpadFragment = getDialpadFragment(); 448 if (dialpadFragment != null) { 449 dialpadFragment.setDtmfText(dtmfTextToPrepopulate); 450 dtmfTextToPrepopulate = null; 451 } 452 } else { 453 LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad"); 454 if (getDialpadFragment() != null) { 455 showDialpadFragment(false /* show */, false /* animate */); 456 } 457 } 458 showDialpadRequest = DIALPAD_REQUEST_NONE; 459 } 460 updateNavigationBar(isDialpadVisible()); 461 462 if (showPostCharWaitDialogOnResume) { 463 showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars); 464 } 465 466 CallList.getInstance() 467 .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false)); 468 469 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState(); 470 pseudoScreenState.addListener(this); 471 onPseudoScreenStateChanged(pseudoScreenState.isOn()); 472 Trace.endSection(); 473 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume. 474 ThreadUtil.postDelayedOnUiThread( 475 () -> 476 MetricsComponent.get(this) 477 .metrics() 478 .recordMemory(Metrics.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME), 479 1000); 480 } 481 482 @Override 483 protected void onPause() { 484 Trace.beginSection("InCallActivity.onPause"); 485 super.onPause(); 486 487 DialpadFragment dialpadFragment = getDialpadFragment(); 488 if (dialpadFragment != null) { 489 dialpadFragment.onDialerKeyUp(null); 490 } 491 492 InCallPresenter.getInstance().updateNotification(); 493 494 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this); 495 Trace.endSection(); 496 } 497 498 @Override 499 protected void onStop() { 500 Trace.beginSection("InCallActivity.onStop"); 501 isVisible = false; 502 super.onStop(); 503 504 // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the 505 // user presses the home button). 506 // Without this the pending call will get stuck on phone account selection and new calls can't 507 // be created. 508 // Skip this when the screen is locked since the activity may complete its current life cycle 509 // and restart. 510 if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) { 511 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall(); 512 if (waitingForAccountCall != null) { 513 waitingForAccountCall.disconnect(); 514 } 515 } 516 517 enableInCallOrientationEventListener(false); 518 InCallPresenter.getInstance().updateIsChangingConfigurations(); 519 InCallPresenter.getInstance().onActivityStopped(); 520 if (!isRecreating) { 521 InCallPresenter.getInstance().onUiShowing(false); 522 if (errorDialog != null) { 523 errorDialog.dismiss(); 524 } 525 } 526 527 if (isFinishing()) { 528 InCallPresenter.getInstance().unsetActivity(this); 529 } 530 531 Trace.endSection(); 532 } 533 534 @Override 535 protected void onDestroy() { 536 Trace.beginSection("InCallActivity.onDestroy"); 537 super.onDestroy(); 538 539 InCallPresenter.getInstance().unsetActivity(this); 540 InCallPresenter.getInstance().updateIsChangingConfigurations(); 541 Trace.endSection(); 542 } 543 544 @Override 545 public void finish() { 546 if (shouldCloseActivityOnFinish()) { 547 // When user select incall ui from recents after the call is disconnected, it tries to launch 548 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes 549 // crash. 550 // By calling finishAndRemoveTask() instead of finish() the task associated with 551 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in 552 // this case. 553 // 554 // Calling finish won't clear the task and normally when an activity finishes it shouldn't 555 // clear the task since there could be parent activity in the same task that's still alive. 556 // But InCallActivity is special since it's singleInstance which means it's root activity and 557 // only instance of activity in the task. So it should be safe to also remove task when 558 // finishing. 559 // It's also necessary in the sense of it's excluded from recents. So whenever the activity 560 // finishes, the task should also be removed since it doesn't make sense to go back to it in 561 // anyway anymore. 562 super.finishAndRemoveTask(); 563 } 564 } 565 566 private boolean shouldCloseActivityOnFinish() { 567 if (!isVisible) { 568 LogUtil.i( 569 "InCallActivity.shouldCloseActivityOnFinish", 570 "allowing activity to be closed because it's not visible"); 571 return true; 572 } 573 574 if (InCallPresenter.getInstance().isInCallUiLocked()) { 575 LogUtil.i( 576 "InCallActivity.shouldCloseActivityOnFinish", 577 "in call ui is locked, not closing activity"); 578 return false; 579 } 580 581 LogUtil.i( 582 "InCallActivity.shouldCloseActivityOnFinish", 583 "activity is visible and has no locks, allowing activity to close"); 584 return true; 585 } 586 587 @Override 588 protected void onNewIntent(Intent intent) { 589 LogUtil.enterBlock("InCallActivity.onNewIntent"); 590 591 // If the screen is off, we need to make sure it gets turned on for incoming calls. 592 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works 593 // when the activity is first created. Therefore, to ensure the screen is turned on 594 // for the call waiting case, we recreate() the current activity. There should be no jank from 595 // this since the screen is already off and will remain so until our new activity is up. 596 if (!isVisible) { 597 onNewIntent(intent, true /* isRecreating */); 598 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on."); 599 recreate(); 600 } else { 601 onNewIntent(intent, false /* isRecreating */); 602 } 603 } 604 605 @VisibleForTesting 606 void onNewIntent(Intent intent, boolean isRecreating) { 607 this.isRecreating = isRecreating; 608 609 // We're being re-launched with a new Intent. Since it's possible for a single InCallActivity 610 // instance to persist indefinitely (even if we finish() ourselves), this sequence can 611 // happen any time the InCallActivity needs to be displayed. 612 613 // Stash away the new intent so that we can get it in the future by calling getIntent(). 614 // Otherwise getIntent() will return the original Intent from when we first got created. 615 setIntent(intent); 616 617 // Activities are always paused before receiving a new intent, so we can count on our onResume() 618 // method being called next. 619 620 // Just like in onCreate(), handle the intent. 621 // Skip if InCallActivity is going to be recreated since this will be called in onCreate(). 622 if (!isRecreating) { 623 internalResolveIntent(intent); 624 } 625 } 626 627 @Override 628 public void onBackPressed() { 629 LogUtil.enterBlock("InCallActivity.onBackPressed"); 630 631 if (!isVisible) { 632 return; 633 } 634 635 if (!getCallCardFragmentVisible()) { 636 return; 637 } 638 639 DialpadFragment dialpadFragment = getDialpadFragment(); 640 if (dialpadFragment != null && dialpadFragment.isVisible()) { 641 showDialpadFragment(false /* show */, true /* animate */); 642 return; 643 } 644 645 if (CallList.getInstance().getIncomingCall() != null) { 646 LogUtil.i( 647 "InCallActivity.onBackPressed", 648 "Ignore the press of the back key when an incoming call is ringing"); 649 return; 650 } 651 652 // Nothing special to do. Fall back to the default behavior. 653 super.onBackPressed(); 654 } 655 656 @Override 657 public boolean onOptionsItemSelected(MenuItem item) { 658 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item); 659 if (item.getItemId() == android.R.id.home) { 660 onBackPressed(); 661 return true; 662 } 663 return super.onOptionsItemSelected(item); 664 } 665 666 @Override 667 public boolean onKeyUp(int keyCode, KeyEvent event) { 668 DialpadFragment dialpadFragment = getDialpadFragment(); 669 if (dialpadFragment != null 670 && dialpadFragment.isVisible() 671 && dialpadFragment.onDialerKeyUp(event)) { 672 return true; 673 } 674 675 if (keyCode == KeyEvent.KEYCODE_CALL) { 676 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it. 677 return true; 678 } 679 680 return super.onKeyUp(keyCode, event); 681 } 682 683 @Override 684 public boolean onKeyDown(int keyCode, KeyEvent event) { 685 switch (keyCode) { 686 case KeyEvent.KEYCODE_CALL: 687 if (!InCallPresenter.getInstance().handleCallKey()) { 688 LogUtil.e( 689 "InCallActivity.onKeyDown", 690 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown"); 691 } 692 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it. 693 return true; 694 695 // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it 696 // is exactly what's needed, namely 697 // (1) "hang up" if there's an active call, or 698 // (2) "don't answer" if there's an incoming call. 699 // (See PhoneWindowManager for implementation details.) 700 701 case KeyEvent.KEYCODE_CAMERA: 702 // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button. 703 return true; 704 705 case KeyEvent.KEYCODE_VOLUME_UP: 706 case KeyEvent.KEYCODE_VOLUME_DOWN: 707 case KeyEvent.KEYCODE_VOLUME_MUTE: 708 // Ringer silencing handled by PhoneWindowManager. 709 break; 710 711 case KeyEvent.KEYCODE_MUTE: 712 TelecomAdapter.getInstance() 713 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted()); 714 return true; 715 716 case KeyEvent.KEYCODE_SLASH: 717 // When verbose logging is enabled, dump the view for debugging/testing purposes. 718 if (LogUtil.isVerboseEnabled()) { 719 View decorView = getWindow().getDecorView(); 720 LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView); 721 return true; 722 } 723 break; 724 725 case KeyEvent.KEYCODE_EQUALS: 726 break; 727 728 default: // fall out 729 } 730 731 // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types 732 // in DTMF (Dual-tone multi-frequency signaling) code. 733 DialpadFragment dialpadFragment = getDialpadFragment(); 734 if (dialpadFragment != null 735 && dialpadFragment.isVisible() 736 && dialpadFragment.onDialerKeyDown(event)) { 737 return true; 738 } 739 740 return super.onKeyDown(keyCode, event); 741 } 742 743 public boolean isInCallScreenAnimating() { 744 return false; 745 } 746 747 public void showConferenceFragment(boolean show) { 748 if (show) { 749 startActivity(new Intent(this, ManageConferenceActivity.class)); 750 } 751 } 752 753 public void showDialpadFragment(boolean show, boolean animate) { 754 if (show == isDialpadVisible()) { 755 return; 756 } 757 758 FragmentManager dialpadFragmentManager = getDialpadFragmentManager(); 759 if (dialpadFragmentManager == null) { 760 LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager"); 761 return; 762 } 763 764 if (!animate) { 765 if (show) { 766 showDialpadFragment(); 767 } else { 768 hideDialpadFragment(); 769 } 770 } else { 771 if (show) { 772 showDialpadFragment(); 773 getDialpadFragment().animateShowDialpad(); 774 } 775 getDialpadFragment() 776 .getView() 777 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation); 778 } 779 780 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor(); 781 if (sensor != null) { 782 sensor.onDialpadVisible(show); 783 } 784 showDialpadRequest = DIALPAD_REQUEST_NONE; 785 786 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB 787 // repositions itself. 788 getInCallScreen().onInCallScreenDialpadVisibilityChange(show); 789 } 790 791 private void showDialpadFragment() { 792 FragmentManager dialpadFragmentManager = getDialpadFragmentManager(); 793 if (dialpadFragmentManager == null) { 794 return; 795 } 796 797 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction(); 798 DialpadFragment dialpadFragment = getDialpadFragment(); 799 if (dialpadFragment == null) { 800 transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT); 801 } else { 802 transaction.show(dialpadFragment); 803 dialpadFragment.setUserVisibleHint(true); 804 } 805 transaction.commitAllowingStateLoss(); 806 dialpadFragmentManager.executePendingTransactions(); 807 808 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this); 809 updateNavigationBar(true /* isDialpadVisible */); 810 } 811 812 private void hideDialpadFragment() { 813 FragmentManager dialpadFragmentManager = getDialpadFragmentManager(); 814 if (dialpadFragmentManager == null) { 815 return; 816 } 817 818 DialpadFragment dialpadFragment = getDialpadFragment(); 819 if (dialpadFragment != null) { 820 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction(); 821 transaction.hide(dialpadFragment); 822 transaction.commitAllowingStateLoss(); 823 dialpadFragmentManager.executePendingTransactions(); 824 dialpadFragment.setUserVisibleHint(false); 825 } 826 updateNavigationBar(false /* isDialpadVisible */); 827 } 828 829 public boolean isDialpadVisible() { 830 DialpadFragment dialpadFragment = getDialpadFragment(); 831 return dialpadFragment != null 832 && dialpadFragment.isAdded() 833 && !dialpadFragment.isHidden() 834 && dialpadFragment.getView() != null 835 && dialpadFragment.getUserVisibleHint(); 836 } 837 838 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */ 839 @Nullable 840 private DialpadFragment getDialpadFragment() { 841 FragmentManager fragmentManager = getDialpadFragmentManager(); 842 if (fragmentManager == null) { 843 return null; 844 } 845 return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT); 846 } 847 848 public void onForegroundCallChanged(DialerCall newForegroundCall) { 849 updateTaskDescription(); 850 851 if (newForegroundCall == null || !didShowAnswerScreen) { 852 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color"); 853 updateWindowBackgroundColor(0 /* progress */); 854 } 855 } 856 857 private void updateTaskDescription() { 858 int color = 859 getResources().getBoolean(R.bool.is_layout_landscape) 860 ? ResourcesCompat.getColor( 861 getResources(), R.color.statusbar_background_color, getTheme()) 862 : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor(); 863 setTaskDescription( 864 new TaskDescription( 865 getResources().getString(R.string.notification_ongoing_call), null /* icon */, color)); 866 } 867 868 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) { 869 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager(); 870 @ColorInt int top; 871 @ColorInt int middle; 872 @ColorInt int bottom; 873 @ColorInt int gray = 0x66000000; 874 875 if (ActivityCompat.isInMultiWindowMode(this)) { 876 top = themeColorManager.getBackgroundColorSolid(); 877 middle = themeColorManager.getBackgroundColorSolid(); 878 bottom = themeColorManager.getBackgroundColorSolid(); 879 } else { 880 top = themeColorManager.getBackgroundColorTop(); 881 middle = themeColorManager.getBackgroundColorMiddle(); 882 bottom = themeColorManager.getBackgroundColorBottom(); 883 } 884 885 if (progress < 0) { 886 float correctedProgress = Math.abs(progress); 887 top = ColorUtils.blendARGB(top, gray, correctedProgress); 888 middle = ColorUtils.blendARGB(middle, gray, correctedProgress); 889 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress); 890 } 891 892 boolean backgroundDirty = false; 893 if (backgroundDrawable == null) { 894 backgroundDrawableColors = new int[] {top, middle, bottom}; 895 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors); 896 backgroundDirty = true; 897 } else { 898 if (backgroundDrawableColors[0] != top) { 899 backgroundDrawableColors[0] = top; 900 backgroundDirty = true; 901 } 902 if (backgroundDrawableColors[1] != middle) { 903 backgroundDrawableColors[1] = middle; 904 backgroundDirty = true; 905 } 906 if (backgroundDrawableColors[2] != bottom) { 907 backgroundDrawableColors[2] = bottom; 908 backgroundDirty = true; 909 } 910 if (backgroundDirty) { 911 backgroundDrawable.setColors(backgroundDrawableColors); 912 } 913 } 914 915 if (backgroundDirty) { 916 getWindow().setBackgroundDrawable(backgroundDrawable); 917 } 918 } 919 920 public boolean isVisible() { 921 return isVisible; 922 } 923 924 public boolean getCallCardFragmentVisible() { 925 return didShowInCallScreen || didShowVideoCallScreen; 926 } 927 928 public void dismissKeyguard(boolean dismiss) { 929 if (dismissKeyguard == dismiss) { 930 return; 931 } 932 933 dismissKeyguard = dismiss; 934 if (dismiss) { 935 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 936 } else { 937 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 938 } 939 } 940 941 public void showDialogForPostCharWait(String callId, String chars) { 942 if (isVisible) { 943 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars); 944 fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT); 945 946 showPostCharWaitDialogOnResume = false; 947 showPostCharWaitDialogCallId = null; 948 showPostCharWaitDialogChars = null; 949 } else { 950 showPostCharWaitDialogOnResume = true; 951 showPostCharWaitDialogCallId = callId; 952 showPostCharWaitDialogChars = chars; 953 } 954 } 955 956 public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) { 957 LogUtil.i( 958 "InCallActivity.showDialogOrToastForDisconnectedCall", 959 "disconnect cause: %s", 960 disconnectMessage); 961 962 if (disconnectMessage.dialog == null || isFinishing()) { 963 return; 964 } 965 966 dismissPendingDialogs(); 967 968 // Show a toast if the app is in background when a dialog can't be visible. 969 if (!isVisible()) { 970 Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG) 971 .show(); 972 return; 973 } 974 975 // Show the dialog. 976 errorDialog = disconnectMessage.dialog; 977 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog"); 978 disconnectMessage.dialog.setOnDismissListener( 979 dialogInterface -> { 980 lock.release(); 981 onDialogDismissed(); 982 }); 983 disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 984 disconnectMessage.dialog.show(); 985 } 986 987 private void onDialogDismissed() { 988 errorDialog = null; 989 CallList.getInstance().onErrorDialogDismissed(); 990 } 991 992 public void dismissPendingDialogs() { 993 LogUtil.enterBlock("InCallActivity.dismissPendingDialogs"); 994 995 if (!isVisible) { 996 // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have 997 // been called. 998 LogUtil.i( 999 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible"); 1000 needDismissPendingDialogs = true; 1001 return; 1002 } 1003 1004 // Dismiss the error dialog 1005 if (errorDialog != null) { 1006 errorDialog.dismiss(); 1007 errorDialog = null; 1008 } 1009 1010 // Dismiss the phone account selection dialog 1011 if (selectPhoneAccountDialogFragment != null) { 1012 selectPhoneAccountDialogFragment.dismiss(); 1013 selectPhoneAccountDialogFragment = null; 1014 } 1015 1016 // Dismiss the dialog for international call on WiFi 1017 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment = 1018 (InternationalCallOnWifiDialogFragment) 1019 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI); 1020 if (internationalCallOnWifiFragment != null) { 1021 internationalCallOnWifiFragment.dismiss(); 1022 } 1023 1024 // Dismiss the answer screen 1025 AnswerScreen answerScreen = getAnswerScreen(); 1026 if (answerScreen != null) { 1027 answerScreen.dismissPendingDialogs(); 1028 } 1029 1030 needDismissPendingDialogs = false; 1031 } 1032 1033 private void enableInCallOrientationEventListener(boolean enable) { 1034 if (enable) { 1035 inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */); 1036 } else { 1037 inCallOrientationEventListener.disable(); 1038 } 1039 } 1040 1041 public void setExcludeFromRecents(boolean exclude) { 1042 int taskId = getTaskId(); 1043 1044 List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks(); 1045 for (AppTask task : tasks) { 1046 try { 1047 if (task.getTaskInfo().id == taskId) { 1048 task.setExcludeFromRecents(exclude); 1049 } 1050 } catch (RuntimeException e) { 1051 LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e); 1052 } 1053 } 1054 } 1055 1056 @Nullable 1057 public FragmentManager getDialpadFragmentManager() { 1058 InCallScreen inCallScreen = getInCallScreen(); 1059 if (inCallScreen != null) { 1060 return inCallScreen.getInCallScreenFragment().getChildFragmentManager(); 1061 } 1062 return null; 1063 } 1064 1065 public int getDialpadContainerId() { 1066 return getInCallScreen().getAnswerAndDialpadContainerResourceId(); 1067 } 1068 1069 @Override 1070 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) { 1071 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId()); 1072 if (call == null) { 1073 // This is a work around for a bug where we attempt to create a new delegate after the call 1074 // has already been removed. An example of when this can happen is: 1075 // 1. incoming video call in landscape mode 1076 // 2. remote party hangs up 1077 // 3. activity switches from landscape to portrait 1078 // At step #3 the answer fragment will try to create a new answer delegate but the call won't 1079 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok 1080 // because this new state is transient and the activity will be destroyed soon. 1081 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub"); 1082 return new AnswerScreenPresenterStub(); 1083 } else { 1084 return new AnswerScreenPresenter( 1085 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId())); 1086 } 1087 } 1088 1089 @Override 1090 public InCallScreenDelegate newInCallScreenDelegate() { 1091 return new CallCardPresenter(this); 1092 } 1093 1094 @Override 1095 public InCallButtonUiDelegate newInCallButtonUiDelegate() { 1096 return new CallButtonPresenter(this); 1097 } 1098 1099 @Override 1100 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) { 1101 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId()); 1102 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) { 1103 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen); 1104 } 1105 return new VideoCallPresenter(); 1106 } 1107 1108 public void onPrimaryCallStateChanged() { 1109 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged"); 1110 showMainInCallFragment(); 1111 Trace.endSection(); 1112 } 1113 1114 public void showToastForWiFiToLteHandover(DialerCall call) { 1115 if (call.hasShownWiFiToLteHandoverToast()) { 1116 return; 1117 } 1118 1119 Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show(); 1120 call.setHasShownWiFiToLteHandoverToast(); 1121 } 1122 1123 public void showDialogOrToastForWifiHandoverFailure(DialerCall call) { 1124 if (call.showWifiHandoverAlertAsToast()) { 1125 Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT) 1126 .show(); 1127 return; 1128 } 1129 1130 dismissPendingDialogs(); 1131 1132 AlertDialog.Builder builder = 1133 new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title); 1134 1135 // This allows us to use the theme of the dialog instead of the activity 1136 View dialogCheckBoxView = 1137 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */); 1138 CheckBox wifiHandoverFailureCheckbox = 1139 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox); 1140 wifiHandoverFailureCheckbox.setChecked(false); 1141 1142 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog"); 1143 errorDialog = 1144 builder 1145 .setView(dialogCheckBoxView) 1146 .setMessage(R.string.video_call_lte_to_wifi_failed_message) 1147 .setOnCancelListener(dialogInterface -> onDialogDismissed()) 1148 .setPositiveButton( 1149 android.R.string.ok, 1150 (dialogInterface, id) -> { 1151 call.setDoNotShowDialogForHandoffToWifiFailure( 1152 wifiHandoverFailureCheckbox.isChecked()); 1153 dialogInterface.cancel(); 1154 onDialogDismissed(); 1155 }) 1156 .setOnDismissListener(dialogInterface -> lock.release()) 1157 .create(); 1158 errorDialog.show(); 1159 } 1160 1161 public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) { 1162 if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) { 1163 LogUtil.i( 1164 "InCallActivity.showDialogForInternationalCallOnWifi", 1165 "InternationalCallOnWifiDialogFragment.shouldShow returned false"); 1166 return; 1167 } 1168 1169 InternationalCallOnWifiDialogFragment fragment = 1170 InternationalCallOnWifiDialogFragment.newInstance( 1171 call.getId(), internationalCallOnWifiCallback); 1172 fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI); 1173 } 1174 1175 @Override 1176 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) { 1177 super.onMultiWindowModeChanged(isInMultiWindowMode); 1178 updateNavigationBar(isDialpadVisible()); 1179 } 1180 1181 private void updateNavigationBar(boolean isDialpadVisible) { 1182 if (ActivityCompat.isInMultiWindowMode(this)) { 1183 return; 1184 } 1185 1186 View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background); 1187 if (navigationBarBackground != null) { 1188 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE); 1189 } 1190 } 1191 1192 public void setAllowOrientationChange(boolean allowOrientationChange) { 1193 if (this.allowOrientationChange == allowOrientationChange) { 1194 return; 1195 } 1196 this.allowOrientationChange = allowOrientationChange; 1197 if (!allowOrientationChange) { 1198 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION); 1199 } else { 1200 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION); 1201 } 1202 enableInCallOrientationEventListener(allowOrientationChange); 1203 } 1204 1205 public void hideMainInCallFragment() { 1206 LogUtil.enterBlock("InCallActivity.hideMainInCallFragment"); 1207 if (getCallCardFragmentVisible()) { 1208 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 1209 hideInCallScreenFragment(transaction); 1210 hideVideoCallScreenFragment(transaction); 1211 transaction.commitAllowingStateLoss(); 1212 getSupportFragmentManager().executePendingTransactions(); 1213 } 1214 } 1215 1216 private void showMainInCallFragment() { 1217 Trace.beginSection("InCallActivity.showMainInCallFragment"); 1218 // If the activity's onStart method hasn't been called yet then defer doing any work. 1219 if (!isVisible) { 1220 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore"); 1221 Trace.endSection(); 1222 return; 1223 } 1224 1225 // Don't let this be reentrant. 1226 if (isInShowMainInCallFragment) { 1227 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing"); 1228 Trace.endSection(); 1229 return; 1230 } 1231 1232 isInShowMainInCallFragment = true; 1233 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi(); 1234 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi(); 1235 ShouldShowUiResult shouldShowRttUi = getShouldShowRttUi(); 1236 LogUtil.i( 1237 "InCallActivity.showMainInCallFragment", 1238 "shouldShowAnswerUi: %b, shouldShowRttUi: %b, shouldShowVideoUi: %b " 1239 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowRttCallScreen: %b, " 1240 + "didShowVideoCallScreen: %b", 1241 shouldShowAnswerUi.shouldShow, 1242 shouldShowRttUi.shouldShow, 1243 shouldShowVideoUi.shouldShow, 1244 didShowAnswerScreen, 1245 didShowInCallScreen, 1246 didShowRttCallScreen, 1247 didShowVideoCallScreen); 1248 // Only video call ui allows orientation change. 1249 setAllowOrientationChange(shouldShowVideoUi.shouldShow); 1250 1251 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 1252 boolean didChange; 1253 if (shouldShowAnswerUi.shouldShow) { 1254 didChange = hideInCallScreenFragment(transaction); 1255 didChange |= hideVideoCallScreenFragment(transaction); 1256 didChange |= hideRttCallScreenFragment(transaction); 1257 didChange |= showAnswerScreenFragment(transaction, shouldShowAnswerUi.call); 1258 } else if (shouldShowVideoUi.shouldShow) { 1259 didChange = hideInCallScreenFragment(transaction); 1260 didChange |= showVideoCallScreenFragment(transaction, shouldShowVideoUi.call); 1261 didChange |= hideRttCallScreenFragment(transaction); 1262 didChange |= hideAnswerScreenFragment(transaction); 1263 } else if (shouldShowRttUi.shouldShow) { 1264 didChange = hideInCallScreenFragment(transaction); 1265 didChange |= hideVideoCallScreenFragment(transaction); 1266 didChange |= hideAnswerScreenFragment(transaction); 1267 didChange |= showRttCallScreenFragment(transaction, shouldShowRttUi.call); 1268 } else { 1269 didChange = showInCallScreenFragment(transaction); 1270 didChange |= hideVideoCallScreenFragment(transaction); 1271 didChange |= hideRttCallScreenFragment(transaction); 1272 didChange |= hideAnswerScreenFragment(transaction); 1273 } 1274 1275 if (didChange) { 1276 Trace.beginSection("InCallActivity.commitTransaction"); 1277 transaction.commitNow(); 1278 Trace.endSection(); 1279 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 1280 } 1281 isInShowMainInCallFragment = false; 1282 Trace.endSection(); 1283 } 1284 1285 private ShouldShowUiResult getShouldShowAnswerUi() { 1286 DialerCall call = CallList.getInstance().getIncomingCall(); 1287 if (call != null) { 1288 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call"); 1289 return new ShouldShowUiResult(true, call); 1290 } 1291 1292 call = CallList.getInstance().getVideoUpgradeRequestCall(); 1293 if (call != null) { 1294 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request"); 1295 return new ShouldShowUiResult(true, call); 1296 } 1297 1298 // Check if we're showing the answer screen and the call is disconnected. If this condition is 1299 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when 1300 // the user rejects an incoming call. 1301 call = CallList.getInstance().getFirstCall(); 1302 if (call == null) { 1303 call = CallList.getInstance().getBackgroundCall(); 1304 } 1305 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) { 1306 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call"); 1307 return new ShouldShowUiResult(true, call); 1308 } 1309 1310 return new ShouldShowUiResult(false, null); 1311 } 1312 1313 private static ShouldShowUiResult getShouldShowVideoUi() { 1314 DialerCall call = CallList.getInstance().getFirstCall(); 1315 if (call == null) { 1316 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call"); 1317 return new ShouldShowUiResult(false, null); 1318 } 1319 1320 if (call.isVideoCall()) { 1321 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call"); 1322 return new ShouldShowUiResult(true, call); 1323 } 1324 1325 if (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()) { 1326 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video"); 1327 return new ShouldShowUiResult(true, call); 1328 } 1329 1330 return new ShouldShowUiResult(false, null); 1331 } 1332 1333 private static ShouldShowUiResult getShouldShowRttUi() { 1334 DialerCall call = CallList.getInstance().getFirstCall(); 1335 if (call == null) { 1336 LogUtil.i("InCallActivity.getShouldShowRttUi", "null call"); 1337 return new ShouldShowUiResult(false, null); 1338 } 1339 1340 if (call.isRttCall()) { 1341 LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call"); 1342 return new ShouldShowUiResult(true, call); 1343 } 1344 1345 if (call.hasSentRttUpgradeRequest()) { 1346 LogUtil.i("InCallActivity.getShouldShowRttUi", "upgrading to rtt"); 1347 return new ShouldShowUiResult(true, call); 1348 } 1349 1350 return new ShouldShowUiResult(false, null); 1351 } 1352 1353 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) { 1354 // When rejecting a call the active call can become null in which case we should continue 1355 // showing the answer screen. 1356 if (didShowAnswerScreen && call == null) { 1357 return false; 1358 } 1359 1360 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null"); 1361 1362 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest(); 1363 1364 // Check if we're already showing an answer screen for this call. 1365 if (didShowAnswerScreen) { 1366 AnswerScreen answerScreen = getAnswerScreen(); 1367 if (answerScreen.getCallId().equals(call.getId()) 1368 && answerScreen.isVideoCall() == call.isVideoCall() 1369 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest 1370 && !answerScreen.isActionTimeout()) { 1371 LogUtil.d( 1372 "InCallActivity.showAnswerScreenFragment", 1373 "answer fragment exists for same call and has NOT been accepted/rejected/timed out"); 1374 return false; 1375 } 1376 if (answerScreen.isActionTimeout()) { 1377 LogUtil.i( 1378 "InCallActivity.showAnswerScreenFragment", 1379 "answer fragment exists but has been accepted/rejected and timed out"); 1380 } else { 1381 LogUtil.i( 1382 "InCallActivity.showAnswerScreenFragment", 1383 "answer fragment exists but arguments do not match"); 1384 } 1385 hideAnswerScreenFragment(transaction); 1386 } 1387 1388 // Show a new answer screen. 1389 AnswerScreen answerScreen = 1390 AnswerBindings.createAnswerScreen( 1391 call.getId(), 1392 call.isRttCall(), 1393 call.isVideoCall(), 1394 isVideoUpgradeRequest, 1395 call.getVideoTech().isSelfManagedCamera(), 1396 shouldAllowAnswerAndRelease(call), 1397 CallList.getInstance().getBackgroundCall() != null); 1398 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN); 1399 1400 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this); 1401 didShowAnswerScreen = true; 1402 return true; 1403 } 1404 1405 private boolean shouldAllowAnswerAndRelease(DialerCall call) { 1406 if (CallList.getInstance().getActiveCall() == null) { 1407 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call"); 1408 return false; 1409 } 1410 if (getSystemService(TelephonyManager.class).getPhoneType() 1411 == TelephonyManager.PHONE_TYPE_CDMA) { 1412 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported"); 1413 return false; 1414 } 1415 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) { 1416 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call"); 1417 return false; 1418 } 1419 if (!ConfigProviderBindings.get(this) 1420 .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) { 1421 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config"); 1422 return false; 1423 } 1424 1425 return true; 1426 } 1427 1428 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) { 1429 if (!didShowAnswerScreen) { 1430 return false; 1431 } 1432 AnswerScreen answerScreen = getAnswerScreen(); 1433 if (answerScreen != null) { 1434 transaction.remove(answerScreen.getAnswerScreenFragment()); 1435 } 1436 1437 didShowAnswerScreen = false; 1438 return true; 1439 } 1440 1441 private boolean showInCallScreenFragment(FragmentTransaction transaction) { 1442 if (didShowInCallScreen) { 1443 return false; 1444 } 1445 InCallScreen inCallScreen = InCallBindings.createInCallScreen(); 1446 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN); 1447 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 1448 didShowInCallScreen = true; 1449 return true; 1450 } 1451 1452 private boolean hideInCallScreenFragment(FragmentTransaction transaction) { 1453 if (!didShowInCallScreen) { 1454 return false; 1455 } 1456 InCallScreen inCallScreen = getInCallScreen(); 1457 if (inCallScreen != null) { 1458 transaction.remove(inCallScreen.getInCallScreenFragment()); 1459 } 1460 didShowInCallScreen = false; 1461 return true; 1462 } 1463 1464 private boolean showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call) { 1465 if (didShowRttCallScreen) { 1466 // This shouldn't happen since only one RTT call is allow at same time. 1467 if (!getRttCallScreen().getCallId().equals(call.getId())) { 1468 LogUtil.e("InCallActivity.showRttCallScreenFragment", "RTT call id doesn't match"); 1469 } 1470 return false; 1471 } 1472 RttCallScreen rttCallScreen = RttBindings.createRttCallScreen(call.getId()); 1473 transaction.add(R.id.main, rttCallScreen.getRttCallScreenFragment(), Tags.RTT_CALL_SCREEN); 1474 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 1475 didShowRttCallScreen = true; 1476 return true; 1477 } 1478 1479 private boolean hideRttCallScreenFragment(FragmentTransaction transaction) { 1480 if (!didShowRttCallScreen) { 1481 return false; 1482 } 1483 RttCallScreen rttCallScreen = getRttCallScreen(); 1484 if (rttCallScreen != null) { 1485 transaction.remove(rttCallScreen.getRttCallScreenFragment()); 1486 } 1487 didShowRttCallScreen = false; 1488 return true; 1489 } 1490 1491 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) { 1492 if (didShowVideoCallScreen) { 1493 VideoCallScreen videoCallScreen = getVideoCallScreen(); 1494 if (videoCallScreen.getCallId().equals(call.getId())) { 1495 return false; 1496 } 1497 LogUtil.i( 1498 "InCallActivity.showVideoCallScreenFragment", 1499 "video call fragment exists but arguments do not match"); 1500 hideVideoCallScreenFragment(transaction); 1501 } 1502 1503 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call); 1504 1505 VideoCallScreen videoCallScreen = 1506 VideoBindings.createVideoCallScreen( 1507 call.getId(), call.getVideoTech().shouldUseSurfaceView()); 1508 transaction.add( 1509 R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN); 1510 1511 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 1512 didShowVideoCallScreen = true; 1513 return true; 1514 } 1515 1516 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) { 1517 if (!didShowVideoCallScreen) { 1518 return false; 1519 } 1520 VideoCallScreen videoCallScreen = getVideoCallScreen(); 1521 if (videoCallScreen != null) { 1522 transaction.remove(videoCallScreen.getVideoCallScreenFragment()); 1523 } 1524 didShowVideoCallScreen = false; 1525 return true; 1526 } 1527 1528 private AnswerScreen getAnswerScreen() { 1529 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN); 1530 } 1531 1532 private InCallScreen getInCallScreen() { 1533 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN); 1534 } 1535 1536 private VideoCallScreen getVideoCallScreen() { 1537 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN); 1538 } 1539 1540 private RttCallScreen getRttCallScreen() { 1541 return (RttCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.RTT_CALL_SCREEN); 1542 } 1543 1544 @Override 1545 public void onPseudoScreenStateChanged(boolean isOn) { 1546 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn); 1547 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE); 1548 } 1549 1550 /** 1551 * For some touch related issue, turning off the screen can be faked by drawing a black view over 1552 * the activity. All touch events started when the screen is "off" is rejected. 1553 * 1554 * @see PseudoScreenState 1555 */ 1556 @Override 1557 public boolean dispatchTouchEvent(MotionEvent event) { 1558 // Reject any gesture that started when the screen is in the fake off state. 1559 if (touchDownWhenPseudoScreenOff) { 1560 if (event.getAction() == MotionEvent.ACTION_UP) { 1561 touchDownWhenPseudoScreenOff = false; 1562 } 1563 return true; 1564 } 1565 // Reject all touch event when the screen is in the fake off state. 1566 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) { 1567 if (event.getAction() == MotionEvent.ACTION_DOWN) { 1568 touchDownWhenPseudoScreenOff = true; 1569 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff"); 1570 } 1571 return true; 1572 } 1573 return super.dispatchTouchEvent(event); 1574 } 1575 1576 @Override 1577 public RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen videoCallScreen) { 1578 return new RttCallPresenter(); 1579 } 1580 1581 private static class ShouldShowUiResult { 1582 public final boolean shouldShow; 1583 public final DialerCall call; 1584 1585 ShouldShowUiResult(boolean shouldShow, DialerCall call) { 1586 this.shouldShow = shouldShow; 1587 this.call = call; 1588 } 1589 } 1590 1591 private static final class IntentExtraNames { 1592 static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent"; 1593 static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call"; 1594 static final String SHOW_DIALPAD = "InCallActivity.show_dialpad"; 1595 } 1596 1597 private static final class KeysForSavedInstance { 1598 static final String DIALPAD_TEXT = "InCallActivity.dialpad_text"; 1599 static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen"; 1600 static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen"; 1601 static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen"; 1602 static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen"; 1603 } 1604 1605 /** Request codes for pending intents. */ 1606 public static final class PendingIntentRequestCodes { 1607 static final int NON_FULL_SCREEN = 0; 1608 static final int FULL_SCREEN = 1; 1609 static final int BUBBLE = 2; 1610 } 1611 1612 private static final class Tags { 1613 static final String ANSWER_SCREEN = "tag_answer_screen"; 1614 static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment"; 1615 static final String IN_CALL_SCREEN = "tag_in_call_screen"; 1616 static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi"; 1617 static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment"; 1618 static final String VIDEO_CALL_SCREEN = "tag_video_call_screen"; 1619 static final String RTT_CALL_SCREEN = "tag_rtt_call_screen"; 1620 static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment"; 1621 } 1622 1623 private static final class ConfigNames { 1624 static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled"; 1625 } 1626 1627 private static final class InternationalCallOnWifiCallback 1628 implements InternationalCallOnWifiDialogFragment.Callback { 1629 private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName(); 1630 1631 @Override 1632 public void continueCall(@NonNull String callId) { 1633 LogUtil.i(TAG, "Continuing call with ID: %s", callId); 1634 } 1635 1636 @Override 1637 public void cancelCall(@NonNull String callId) { 1638 DialerCall call = CallList.getInstance().getCallById(callId); 1639 if (call == null) { 1640 LogUtil.i(TAG, "Call destroyed before the dialog is closed"); 1641 return; 1642 } 1643 1644 LogUtil.i(TAG, "Disconnecting international call on WiFi"); 1645 call.disconnect(); 1646 } 1647 } 1648 1649 private static final class SelectPhoneAccountListener 1650 extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener { 1651 private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName(); 1652 1653 @Override 1654 public void onPhoneAccountSelected( 1655 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) { 1656 DialerCall call = CallList.getInstance().getCallById(callId); 1657 LogUtil.i(TAG, "Phone account select with call:\n%s", call); 1658 1659 if (call != null) { 1660 call.phoneAccountSelected(selectedAccountHandle, setDefault); 1661 } 1662 } 1663 1664 @Override 1665 public void onDialogDismissed(String callId) { 1666 DialerCall call = CallList.getInstance().getCallById(callId); 1667 LogUtil.i(TAG, "Disconnecting call:\n%s" + call); 1668 1669 if (call != null) { 1670 call.disconnect(); 1671 } 1672 } 1673 } 1674 } 1675