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