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.content.Context; 20 import android.content.Intent; 21 import android.graphics.drawable.GradientDrawable; 22 import android.graphics.drawable.GradientDrawable.Orientation; 23 import android.os.Bundle; 24 import android.support.annotation.ColorInt; 25 import android.support.annotation.FloatRange; 26 import android.support.annotation.NonNull; 27 import android.support.annotation.Nullable; 28 import android.support.v4.app.FragmentManager; 29 import android.support.v4.app.FragmentTransaction; 30 import android.support.v4.graphics.ColorUtils; 31 import android.telecom.DisconnectCause; 32 import android.telephony.TelephonyManager; 33 import android.view.KeyEvent; 34 import android.view.MenuItem; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import com.android.dialer.common.Assert; 38 import com.android.dialer.common.ConfigProviderBindings; 39 import com.android.dialer.common.LogUtil; 40 import com.android.dialer.compat.ActivityCompat; 41 import com.android.dialer.logging.Logger; 42 import com.android.dialer.logging.ScreenEvent; 43 import com.android.incallui.answer.bindings.AnswerBindings; 44 import com.android.incallui.answer.protocol.AnswerScreen; 45 import com.android.incallui.answer.protocol.AnswerScreenDelegate; 46 import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory; 47 import com.android.incallui.answerproximitysensor.PseudoScreenState; 48 import com.android.incallui.call.CallList; 49 import com.android.incallui.call.DialerCall; 50 import com.android.incallui.call.DialerCall.State; 51 import com.android.incallui.incall.bindings.InCallBindings; 52 import com.android.incallui.incall.protocol.InCallButtonUiDelegate; 53 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; 54 import com.android.incallui.incall.protocol.InCallScreen; 55 import com.android.incallui.incall.protocol.InCallScreenDelegate; 56 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; 57 import com.android.incallui.video.bindings.VideoBindings; 58 import com.android.incallui.video.protocol.VideoCallScreen; 59 import com.android.incallui.video.protocol.VideoCallScreenDelegate; 60 import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory; 61 62 /** Version of {@link InCallActivity} that shows the new UI */ 63 public class InCallActivity extends TransactionSafeFragmentActivity 64 implements AnswerScreenDelegateFactory, 65 InCallScreenDelegateFactory, 66 InCallButtonUiDelegateFactory, 67 VideoCallScreenDelegateFactory, 68 PseudoScreenState.StateChangedListener { 69 70 private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen"; 71 private static final String TAG_ANSWER_SCREEN = "tag_answer_screen"; 72 private static final String TAG_VIDEO_CALL_SCREEN = "tag_video_call_screen"; 73 74 private static final String DID_SHOW_ANSWER_SCREEN_KEY = "did_show_answer_screen"; 75 private static final String DID_SHOW_IN_CALL_SCREEN_KEY = "did_show_in_call_screen"; 76 private static final String DID_SHOW_VIDEO_CALL_SCREEN_KEY = "did_show_video_call_screen"; 77 78 private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled"; 79 80 private final InCallActivityCommon common; 81 private boolean didShowAnswerScreen; 82 private boolean didShowInCallScreen; 83 private boolean didShowVideoCallScreen; 84 private int[] backgroundDrawableColors; 85 private GradientDrawable backgroundDrawable; 86 private boolean isVisible; 87 private View pseudoBlackScreenOverlay; 88 private boolean touchDownWhenPseudoScreenOff; 89 private boolean isInShowMainInCallFragment; 90 private boolean needDismissPendingDialogs; 91 InCallActivity()92 public InCallActivity() { 93 common = new InCallActivityCommon(this); 94 } 95 getIntent( Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen)96 public static Intent getIntent( 97 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) { 98 Intent intent = new Intent(Intent.ACTION_MAIN, null); 99 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); 100 intent.setClass(context, InCallActivity.class); 101 InCallActivityCommon.setIntentExtras(intent, showDialpad, newOutgoingCall, isForFullScreen); 102 return intent; 103 } 104 105 @Override onResumeFragments()106 protected void onResumeFragments() { 107 super.onResumeFragments(); 108 if (needDismissPendingDialogs) { 109 dismissPendingDialogs(); 110 } 111 } 112 113 @Override onCreate(Bundle icicle)114 protected void onCreate(Bundle icicle) { 115 LogUtil.i("InCallActivity.onCreate", ""); 116 super.onCreate(icicle); 117 118 if (icicle != null) { 119 didShowAnswerScreen = icicle.getBoolean(DID_SHOW_ANSWER_SCREEN_KEY); 120 didShowInCallScreen = icicle.getBoolean(DID_SHOW_IN_CALL_SCREEN_KEY); 121 didShowVideoCallScreen = icicle.getBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY); 122 } 123 124 common.onCreate(icicle); 125 126 getWindow() 127 .getDecorView() 128 .setSystemUiVisibility( 129 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 130 131 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay); 132 } 133 134 @Override onSaveInstanceState(Bundle out)135 protected void onSaveInstanceState(Bundle out) { 136 LogUtil.i("InCallActivity.onSaveInstanceState", ""); 137 common.onSaveInstanceState(out); 138 out.putBoolean(DID_SHOW_ANSWER_SCREEN_KEY, didShowAnswerScreen); 139 out.putBoolean(DID_SHOW_IN_CALL_SCREEN_KEY, didShowInCallScreen); 140 out.putBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY, didShowVideoCallScreen); 141 super.onSaveInstanceState(out); 142 isVisible = false; 143 } 144 145 @Override onStart()146 protected void onStart() { 147 LogUtil.i("InCallActivity.onStart", ""); 148 super.onStart(); 149 isVisible = true; 150 showMainInCallFragment(); 151 common.onStart(); 152 if (ActivityCompat.isInMultiWindowMode(this) 153 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) { 154 // Hide the dialpad because there may not be enough room 155 showDialpadFragment(false, false); 156 } 157 } 158 159 @Override onResume()160 protected void onResume() { 161 LogUtil.i("InCallActivity.onResume", ""); 162 super.onResume(); 163 common.onResume(); 164 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState(); 165 pseudoScreenState.addListener(this); 166 onPseudoScreenStateChanged(pseudoScreenState.isOn()); 167 } 168 169 /** onPause is guaranteed to be called when the InCallActivity goes in the background. */ 170 @Override onPause()171 protected void onPause() { 172 LogUtil.i("InCallActivity.onPause", ""); 173 super.onPause(); 174 common.onPause(); 175 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this); 176 } 177 178 @Override onStop()179 protected void onStop() { 180 LogUtil.i("InCallActivity.onStop", ""); 181 super.onStop(); 182 common.onStop(); 183 isVisible = false; 184 } 185 186 @Override onDestroy()187 protected void onDestroy() { 188 LogUtil.i("InCallActivity.onDestroy", ""); 189 super.onDestroy(); 190 common.onDestroy(); 191 } 192 193 @Override finish()194 public void finish() { 195 if (shouldCloseActivityOnFinish()) { 196 // When user select incall ui from recents after the call is disconnected, it tries to launch 197 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes 198 // crash. 199 // By calling finishAndRemoveTask() instead of finish() the task associated with 200 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in 201 // this case. 202 // 203 // Calling finish won't clear the task and normally when an activity finishes it shouldn't 204 // clear the task since there could be parent activity in the same task that's still alive. 205 // But InCallActivity is special since it's singleInstance which means it's root activity and 206 // only instance of activity in the task. So it should be safe to also remove task when 207 // finishing. 208 // It's also necessary in the sense of it's excluded from recents. So whenever the activity 209 // finishes, the task should also be removed since it doesn't make sense to go back to it in 210 // anyway anymore. 211 super.finishAndRemoveTask(); 212 } 213 } 214 shouldCloseActivityOnFinish()215 private boolean shouldCloseActivityOnFinish() { 216 if (!isVisible()) { 217 LogUtil.i( 218 "InCallActivity.shouldCloseActivityOnFinish", 219 "allowing activity to be closed because it's not visible"); 220 return true; 221 } 222 223 if (common.hasPendingDialogs()) { 224 LogUtil.i( 225 "InCallActivity.shouldCloseActivityOnFinish", "dialog is visible, not closing activity"); 226 return false; 227 } 228 229 AnswerScreen answerScreen = getAnswerScreen(); 230 if (answerScreen != null && answerScreen.hasPendingDialogs()) { 231 LogUtil.i( 232 "InCallActivity.shouldCloseActivityOnFinish", 233 "answer screen dialog is visible, not closing activity"); 234 return false; 235 } 236 237 LogUtil.i( 238 "InCallActivity.shouldCloseActivityOnFinish", 239 "activity is visible and has no dialogs, allowing activity to close"); 240 return true; 241 } 242 243 @Override onNewIntent(Intent intent)244 protected void onNewIntent(Intent intent) { 245 LogUtil.i("InCallActivity.onNewIntent", ""); 246 247 // If the screen is off, we need to make sure it gets turned on for incoming calls. 248 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works 249 // when the activity is first created. Therefore, to ensure the screen is turned on 250 // for the call waiting case, we recreate() the current activity. There should be no jank from 251 // this since the screen is already off and will remain so until our new activity is up. 252 if (!isVisible()) { 253 common.onNewIntent(intent, true /* isRecreating */); 254 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on."); 255 recreate(); 256 } else { 257 common.onNewIntent(intent, false /* isRecreating */); 258 } 259 } 260 261 @Override onBackPressed()262 public void onBackPressed() { 263 LogUtil.i("InCallActivity.onBackPressed", ""); 264 if (!common.onBackPressed(didShowInCallScreen || didShowVideoCallScreen)) { 265 super.onBackPressed(); 266 } 267 } 268 269 @Override onOptionsItemSelected(MenuItem item)270 public boolean onOptionsItemSelected(MenuItem item) { 271 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item); 272 if (item.getItemId() == android.R.id.home) { 273 onBackPressed(); 274 return true; 275 } 276 return super.onOptionsItemSelected(item); 277 } 278 279 @Override onKeyUp(int keyCode, KeyEvent event)280 public boolean onKeyUp(int keyCode, KeyEvent event) { 281 return common.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); 282 } 283 284 @Override onKeyDown(int keyCode, KeyEvent event)285 public boolean onKeyDown(int keyCode, KeyEvent event) { 286 return common.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); 287 } 288 isInCallScreenAnimating()289 public boolean isInCallScreenAnimating() { 290 return false; 291 } 292 showConferenceFragment(boolean show)293 public void showConferenceFragment(boolean show) { 294 if (show) { 295 startActivity(new Intent(this, ManageConferenceActivity.class)); 296 } 297 } 298 showDialpadFragment(boolean show, boolean animate)299 public boolean showDialpadFragment(boolean show, boolean animate) { 300 boolean didChange = common.showDialpadFragment(show, animate); 301 if (didChange) { 302 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB 303 // repositions itself. 304 getInCallScreen().onInCallScreenDialpadVisibilityChange(show); 305 } 306 return didChange; 307 } 308 isDialpadVisible()309 public boolean isDialpadVisible() { 310 return common.isDialpadVisible(); 311 } 312 onForegroundCallChanged(DialerCall newForegroundCall)313 public void onForegroundCallChanged(DialerCall newForegroundCall) { 314 common.updateTaskDescription(); 315 if (didShowAnswerScreen && newForegroundCall != null) { 316 if (newForegroundCall.getState() == State.DISCONNECTED 317 || newForegroundCall.getState() == State.IDLE) { 318 LogUtil.i( 319 "InCallActivity.onForegroundCallChanged", 320 "rejecting incoming call, not updating " + "window background color"); 321 } 322 } else { 323 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color"); 324 updateWindowBackgroundColor(0); 325 } 326 } 327 updateWindowBackgroundColor(@loatRangefrom = -1f, to = 1.0f) float progress)328 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) { 329 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager(); 330 @ColorInt int top; 331 @ColorInt int middle; 332 @ColorInt int bottom; 333 @ColorInt int gray = 0x66000000; 334 335 if (ActivityCompat.isInMultiWindowMode(this)) { 336 top = themeColorManager.getBackgroundColorSolid(); 337 middle = themeColorManager.getBackgroundColorSolid(); 338 bottom = themeColorManager.getBackgroundColorSolid(); 339 } else { 340 top = themeColorManager.getBackgroundColorTop(); 341 middle = themeColorManager.getBackgroundColorMiddle(); 342 bottom = themeColorManager.getBackgroundColorBottom(); 343 } 344 345 if (progress < 0) { 346 float correctedProgress = Math.abs(progress); 347 top = ColorUtils.blendARGB(top, gray, correctedProgress); 348 middle = ColorUtils.blendARGB(middle, gray, correctedProgress); 349 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress); 350 } 351 352 boolean backgroundDirty = false; 353 if (backgroundDrawable == null) { 354 backgroundDrawableColors = new int[] {top, middle, bottom}; 355 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors); 356 backgroundDirty = true; 357 } else { 358 if (backgroundDrawableColors[0] != top) { 359 backgroundDrawableColors[0] = top; 360 backgroundDirty = true; 361 } 362 if (backgroundDrawableColors[1] != middle) { 363 backgroundDrawableColors[1] = middle; 364 backgroundDirty = true; 365 } 366 if (backgroundDrawableColors[2] != bottom) { 367 backgroundDrawableColors[2] = bottom; 368 backgroundDirty = true; 369 } 370 if (backgroundDirty) { 371 backgroundDrawable.setColors(backgroundDrawableColors); 372 } 373 } 374 375 if (backgroundDirty) { 376 getWindow().setBackgroundDrawable(backgroundDrawable); 377 } 378 } 379 isVisible()380 public boolean isVisible() { 381 return isVisible; 382 } 383 getCallCardFragmentVisible()384 public boolean getCallCardFragmentVisible() { 385 return didShowInCallScreen || didShowVideoCallScreen; 386 } 387 dismissKeyguard(boolean dismiss)388 public void dismissKeyguard(boolean dismiss) { 389 common.dismissKeyguard(dismiss); 390 } 391 showPostCharWaitDialog(String callId, String chars)392 public void showPostCharWaitDialog(String callId, String chars) { 393 common.showPostCharWaitDialog(callId, chars); 394 } 395 maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause)396 public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) { 397 common.maybeShowErrorDialogOnDisconnect(disconnectCause); 398 } 399 dismissPendingDialogs()400 public void dismissPendingDialogs() { 401 if (isVisible) { 402 LogUtil.i("InCallActivity.dismissPendingDialogs", ""); 403 common.dismissPendingDialogs(); 404 AnswerScreen answerScreen = getAnswerScreen(); 405 if (answerScreen != null) { 406 answerScreen.dismissPendingDialogs(); 407 } 408 needDismissPendingDialogs = false; 409 } else { 410 // The activity is not visible and onSaveInstanceState may have been called so defer the 411 // dismissing action. 412 LogUtil.i( 413 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible"); 414 needDismissPendingDialogs = true; 415 } 416 } 417 enableInCallOrientationEventListener(boolean enable)418 private void enableInCallOrientationEventListener(boolean enable) { 419 common.enableInCallOrientationEventListener(enable); 420 } 421 setExcludeFromRecents(boolean exclude)422 public void setExcludeFromRecents(boolean exclude) { 423 common.setExcludeFromRecents(exclude); 424 } 425 426 @Nullable getDialpadFragmentManager()427 public FragmentManager getDialpadFragmentManager() { 428 InCallScreen inCallScreen = getInCallScreen(); 429 if (inCallScreen != null) { 430 return inCallScreen.getInCallScreenFragment().getChildFragmentManager(); 431 } 432 return null; 433 } 434 getDialpadContainerId()435 public int getDialpadContainerId() { 436 return getInCallScreen().getAnswerAndDialpadContainerResourceId(); 437 } 438 439 @Override newAnswerScreenDelegate(AnswerScreen answerScreen)440 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) { 441 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId()); 442 if (call == null) { 443 // This is a work around for a bug where we attempt to create a new delegate after the call 444 // has already been removed. An example of when this can happen is: 445 // 1. incoming video call in landscape mode 446 // 2. remote party hangs up 447 // 3. activity switches from landscape to portrait 448 // At step #3 the answer fragment will try to create a new answer delegate but the call won't 449 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok 450 // because this new state is transient and the activity will be destroyed soon. 451 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub"); 452 return new AnswerScreenPresenterStub(); 453 } else { 454 return new AnswerScreenPresenter( 455 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId())); 456 } 457 } 458 459 @Override newInCallScreenDelegate()460 public InCallScreenDelegate newInCallScreenDelegate() { 461 return new CallCardPresenter(this); 462 } 463 464 @Override newInCallButtonUiDelegate()465 public InCallButtonUiDelegate newInCallButtonUiDelegate() { 466 return new CallButtonPresenter(this); 467 } 468 469 @Override newVideoCallScreenDelegate(VideoCallScreen videoCallScreen)470 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) { 471 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId()); 472 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) { 473 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen); 474 } 475 return new VideoCallPresenter(); 476 } 477 onPrimaryCallStateChanged()478 public void onPrimaryCallStateChanged() { 479 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", ""); 480 showMainInCallFragment(); 481 } 482 onWiFiToLteHandover(DialerCall call)483 public void onWiFiToLteHandover(DialerCall call) { 484 common.showWifiToLteHandoverToast(call); 485 } 486 onHandoverToWifiFailed(DialerCall call)487 public void onHandoverToWifiFailed(DialerCall call) { 488 common.showWifiFailedDialog(call); 489 } 490 onInternationalCallOnWifi(@onNull DialerCall call)491 public void onInternationalCallOnWifi(@NonNull DialerCall call) { 492 LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi"); 493 common.showInternationalCallOnWifiDialog(call); 494 } 495 setAllowOrientationChange(boolean allowOrientationChange)496 public void setAllowOrientationChange(boolean allowOrientationChange) { 497 if (!allowOrientationChange) { 498 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION); 499 } else { 500 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION); 501 } 502 enableInCallOrientationEventListener(allowOrientationChange); 503 } 504 hideMainInCallFragment()505 public void hideMainInCallFragment() { 506 LogUtil.i("InCallActivity.hideMainInCallFragment", ""); 507 if (didShowInCallScreen || didShowVideoCallScreen) { 508 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 509 hideInCallScreenFragment(transaction); 510 hideVideoCallScreenFragment(transaction); 511 transaction.commitAllowingStateLoss(); 512 getSupportFragmentManager().executePendingTransactions(); 513 } 514 } 515 showMainInCallFragment()516 private void showMainInCallFragment() { 517 // If the activity's onStart method hasn't been called yet then defer doing any work. 518 if (!isVisible) { 519 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore"); 520 return; 521 } 522 523 // Don't let this be reentrant. 524 if (isInShowMainInCallFragment) { 525 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing"); 526 return; 527 } 528 529 isInShowMainInCallFragment = true; 530 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi(); 531 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi(); 532 LogUtil.i( 533 "InCallActivity.showMainInCallFragment", 534 "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, " 535 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b", 536 shouldShowAnswerUi.shouldShow, 537 shouldShowVideoUi.shouldShow, 538 didShowAnswerScreen, 539 didShowInCallScreen, 540 didShowVideoCallScreen); 541 // Only video call ui allows orientation change. 542 setAllowOrientationChange(shouldShowVideoUi.shouldShow); 543 544 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); 545 boolean didChangeInCall; 546 boolean didChangeVideo; 547 boolean didChangeAnswer; 548 if (shouldShowAnswerUi.shouldShow) { 549 didChangeInCall = hideInCallScreenFragment(transaction); 550 didChangeVideo = hideVideoCallScreenFragment(transaction); 551 didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call); 552 } else if (shouldShowVideoUi.shouldShow) { 553 didChangeInCall = hideInCallScreenFragment(transaction); 554 didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call); 555 didChangeAnswer = hideAnswerScreenFragment(transaction); 556 } else { 557 didChangeInCall = showInCallScreenFragment(transaction); 558 didChangeVideo = hideVideoCallScreenFragment(transaction); 559 didChangeAnswer = hideAnswerScreenFragment(transaction); 560 } 561 562 if (didChangeInCall || didChangeVideo || didChangeAnswer) { 563 transaction.commitNow(); 564 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 565 } 566 isInShowMainInCallFragment = false; 567 } 568 getShouldShowAnswerUi()569 private ShouldShowUiResult getShouldShowAnswerUi() { 570 DialerCall call = CallList.getInstance().getIncomingCall(); 571 if (call != null) { 572 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call"); 573 return new ShouldShowUiResult(true, call); 574 } 575 576 call = CallList.getInstance().getVideoUpgradeRequestCall(); 577 if (call != null) { 578 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request"); 579 return new ShouldShowUiResult(true, call); 580 } 581 582 // Check if we're showing the answer screen and the call is disconnected. If this condition is 583 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when 584 // the user rejects an incoming call. 585 call = CallList.getInstance().getFirstCall(); 586 if (call == null) { 587 call = CallList.getInstance().getBackgroundCall(); 588 } 589 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) { 590 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call"); 591 return new ShouldShowUiResult(true, call); 592 } 593 594 return new ShouldShowUiResult(false, null); 595 } 596 getShouldShowVideoUi()597 private static ShouldShowUiResult getShouldShowVideoUi() { 598 DialerCall call = CallList.getInstance().getFirstCall(); 599 if (call == null) { 600 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call"); 601 return new ShouldShowUiResult(false, null); 602 } 603 604 if (call.isVideoCall()) { 605 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call"); 606 return new ShouldShowUiResult(true, call); 607 } 608 609 if (call.hasSentVideoUpgradeRequest()) { 610 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video"); 611 return new ShouldShowUiResult(true, call); 612 } 613 614 return new ShouldShowUiResult(false, null); 615 } 616 showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call)617 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) { 618 // When rejecting a call the active call can become null in which case we should continue 619 // showing the answer screen. 620 if (didShowAnswerScreen && call == null) { 621 return false; 622 } 623 624 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null"); 625 626 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest(); 627 628 // Check if we're already showing an answer screen for this call. 629 if (didShowAnswerScreen) { 630 AnswerScreen answerScreen = getAnswerScreen(); 631 if (answerScreen.getCallId().equals(call.getId()) 632 && answerScreen.isVideoCall() == call.isVideoCall() 633 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest) { 634 return false; 635 } 636 LogUtil.i( 637 "InCallActivity.showAnswerScreenFragment", 638 "answer fragment exists but arguments do not match"); 639 hideAnswerScreenFragment(transaction); 640 } 641 642 // Show a new answer screen. 643 AnswerScreen answerScreen = 644 AnswerBindings.createAnswerScreen( 645 call.getId(), 646 call.isVideoCall(), 647 isVideoUpgradeRequest, 648 call.getVideoTech().isSelfManagedCamera(), 649 shouldAllowAnswerAndRelease(call), 650 CallList.getInstance().getBackgroundCall() != null); 651 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), TAG_ANSWER_SCREEN); 652 653 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this); 654 didShowAnswerScreen = true; 655 return true; 656 } 657 shouldAllowAnswerAndRelease(DialerCall call)658 private boolean shouldAllowAnswerAndRelease(DialerCall call) { 659 if (CallList.getInstance().getActiveCall() == null) { 660 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call"); 661 return false; 662 } 663 if (getSystemService(TelephonyManager.class).getPhoneType() 664 == TelephonyManager.PHONE_TYPE_CDMA) { 665 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported"); 666 return false; 667 } 668 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) { 669 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call"); 670 return false; 671 } 672 if (!ConfigProviderBindings.get(this).getBoolean(CONFIG_ANSWER_AND_RELEASE_ENABLED, true)) { 673 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config"); 674 return false; 675 } 676 677 return true; 678 } 679 hideAnswerScreenFragment(FragmentTransaction transaction)680 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) { 681 if (!didShowAnswerScreen) { 682 return false; 683 } 684 AnswerScreen answerScreen = getAnswerScreen(); 685 if (answerScreen != null) { 686 transaction.remove(answerScreen.getAnswerScreenFragment()); 687 } 688 689 didShowAnswerScreen = false; 690 return true; 691 } 692 showInCallScreenFragment(FragmentTransaction transaction)693 private boolean showInCallScreenFragment(FragmentTransaction transaction) { 694 if (didShowInCallScreen) { 695 return false; 696 } 697 InCallScreen inCallScreen = getInCallScreen(); 698 if (inCallScreen == null) { 699 inCallScreen = InCallBindings.createInCallScreen(); 700 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), TAG_IN_CALL_SCREEN); 701 } else { 702 transaction.show(inCallScreen.getInCallScreenFragment()); 703 } 704 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 705 didShowInCallScreen = true; 706 return true; 707 } 708 hideInCallScreenFragment(FragmentTransaction transaction)709 private boolean hideInCallScreenFragment(FragmentTransaction transaction) { 710 if (!didShowInCallScreen) { 711 return false; 712 } 713 InCallScreen inCallScreen = getInCallScreen(); 714 if (inCallScreen != null) { 715 transaction.hide(inCallScreen.getInCallScreenFragment()); 716 } 717 didShowInCallScreen = false; 718 return true; 719 } 720 showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call)721 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) { 722 if (didShowVideoCallScreen) { 723 VideoCallScreen videoCallScreen = getVideoCallScreen(); 724 if (videoCallScreen.getCallId().equals(call.getId())) { 725 return false; 726 } 727 LogUtil.i( 728 "InCallActivity.showVideoCallScreenFragment", 729 "video call fragment exists but arguments do not match"); 730 hideVideoCallScreenFragment(transaction); 731 } 732 733 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call); 734 735 VideoCallScreen videoCallScreen = 736 VideoBindings.createVideoCallScreen( 737 call.getId(), call.getVideoTech().shouldUseSurfaceView()); 738 transaction.add(R.id.main, videoCallScreen.getVideoCallScreenFragment(), TAG_VIDEO_CALL_SCREEN); 739 740 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this); 741 didShowVideoCallScreen = true; 742 return true; 743 } 744 hideVideoCallScreenFragment(FragmentTransaction transaction)745 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) { 746 if (!didShowVideoCallScreen) { 747 return false; 748 } 749 VideoCallScreen videoCallScreen = getVideoCallScreen(); 750 if (videoCallScreen != null) { 751 transaction.remove(videoCallScreen.getVideoCallScreenFragment()); 752 } 753 didShowVideoCallScreen = false; 754 return true; 755 } 756 getAnswerScreen()757 AnswerScreen getAnswerScreen() { 758 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(TAG_ANSWER_SCREEN); 759 } 760 getInCallScreen()761 InCallScreen getInCallScreen() { 762 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_IN_CALL_SCREEN); 763 } 764 getVideoCallScreen()765 VideoCallScreen getVideoCallScreen() { 766 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_VIDEO_CALL_SCREEN); 767 } 768 769 @Override onPseudoScreenStateChanged(boolean isOn)770 public void onPseudoScreenStateChanged(boolean isOn) { 771 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn); 772 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE); 773 } 774 775 /** 776 * For some touch related issue, turning off the screen can be faked by drawing a black view over 777 * the activity. All touch events started when the screen is "off" is rejected. 778 * 779 * @see PseudoScreenState 780 */ 781 @Override dispatchTouchEvent(MotionEvent event)782 public boolean dispatchTouchEvent(MotionEvent event) { 783 // Reject any gesture that started when the screen is in the fake off state. 784 if (touchDownWhenPseudoScreenOff) { 785 if (event.getAction() == MotionEvent.ACTION_UP) { 786 touchDownWhenPseudoScreenOff = false; 787 } 788 return true; 789 } 790 // Reject all touch event when the screen is in the fake off state. 791 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) { 792 if (event.getAction() == MotionEvent.ACTION_DOWN) { 793 touchDownWhenPseudoScreenOff = true; 794 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff"); 795 } 796 return true; 797 } 798 return super.dispatchTouchEvent(event); 799 } 800 801 private static class ShouldShowUiResult { 802 public final boolean shouldShow; 803 public final DialerCall call; 804 ShouldShowUiResult(boolean shouldShow, DialerCall call)805 ShouldShowUiResult(boolean shouldShow, DialerCall call) { 806 this.shouldShow = shouldShow; 807 this.call = call; 808 } 809 } 810 } 811