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