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.content.Context;
     25 import android.content.DialogInterface;
     26 import android.content.DialogInterface.OnCancelListener;
     27 import android.content.DialogInterface.OnDismissListener;
     28 import android.content.Intent;
     29 import android.content.res.Configuration;
     30 import android.content.res.Resources;
     31 import android.os.Bundle;
     32 import android.support.annotation.IntDef;
     33 import android.support.annotation.NonNull;
     34 import android.support.annotation.Nullable;
     35 import android.support.v4.app.Fragment;
     36 import android.support.v4.app.FragmentManager;
     37 import android.support.v4.app.FragmentTransaction;
     38 import android.support.v4.content.res.ResourcesCompat;
     39 import android.telecom.DisconnectCause;
     40 import android.telecom.PhoneAccountHandle;
     41 import android.text.TextUtils;
     42 import android.util.Pair;
     43 import android.view.KeyEvent;
     44 import android.view.View;
     45 import android.view.Window;
     46 import android.view.WindowManager;
     47 import android.view.animation.Animation;
     48 import android.view.animation.AnimationUtils;
     49 import android.widget.CheckBox;
     50 import android.widget.Toast;
     51 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
     52 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
     53 import com.android.dialer.animation.AnimUtils;
     54 import com.android.dialer.animation.AnimationListenerAdapter;
     55 import com.android.dialer.common.LogUtil;
     56 import com.android.dialer.compat.CompatUtils;
     57 import com.android.dialer.logging.Logger;
     58 import com.android.dialer.logging.ScreenEvent;
     59 import com.android.dialer.util.ViewUtil;
     60 import com.android.incallui.audiomode.AudioModeProvider;
     61 import com.android.incallui.call.CallList;
     62 import com.android.incallui.call.DialerCall;
     63 import com.android.incallui.call.DialerCall.State;
     64 import com.android.incallui.call.TelecomAdapter;
     65 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
     66 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
     67 import com.android.incallui.wifi.EnableWifiCallingPrompt;
     68 import java.lang.annotation.Retention;
     69 import java.lang.annotation.RetentionPolicy;
     70 import java.util.ArrayList;
     71 import java.util.List;
     72 
     73 /** Shared functionality between the new and old in call activity. */
     74 public class InCallActivityCommon {
     75 
     76   private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
     77   private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
     78   private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
     79       "InCallActivity.for_full_screen_intent";
     80 
     81   private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
     82 
     83   private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
     84   private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
     85   private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
     86 
     87   @Retention(RetentionPolicy.SOURCE)
     88   @IntDef({
     89     DIALPAD_REQUEST_NONE,
     90     DIALPAD_REQUEST_SHOW,
     91     DIALPAD_REQUEST_HIDE,
     92   })
     93   @interface DialpadRequestType {}
     94 
     95   private static final int DIALPAD_REQUEST_NONE = 1;
     96   private static final int DIALPAD_REQUEST_SHOW = 2;
     97   private static final int DIALPAD_REQUEST_HIDE = 3;
     98 
     99   private final InCallActivity inCallActivity;
    100   private boolean dismissKeyguard;
    101   private boolean showPostCharWaitDialogOnResume;
    102   private String showPostCharWaitDialogCallId;
    103   private String showPostCharWaitDialogChars;
    104   private Dialog dialog;
    105   private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
    106   private InCallOrientationEventListener inCallOrientationEventListener;
    107   private Animation dialpadSlideInAnimation;
    108   private Animation dialpadSlideOutAnimation;
    109   private boolean animateDialpadOnShow;
    110   private String dtmfTextToPreopulate;
    111   @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
    112 
    113   private final SelectPhoneAccountListener selectAccountListener =
    114       new SelectPhoneAccountListener() {
    115         @Override
    116         public void onPhoneAccountSelected(
    117             PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
    118           DialerCall call = CallList.getInstance().getCallById(callId);
    119           LogUtil.i(
    120               "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
    121               "call: " + call);
    122           if (call != null) {
    123             call.phoneAccountSelected(selectedAccountHandle, setDefault);
    124           }
    125         }
    126 
    127         @Override
    128         public void onDialogDismissed(String callId) {
    129           DialerCall call = CallList.getInstance().getCallById(callId);
    130           LogUtil.i(
    131               "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
    132               "disconnecting call: " + call);
    133           if (call != null) {
    134             call.disconnect();
    135           }
    136         }
    137       };
    138 
    139   private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
    140       new Callback() {
    141         @Override
    142         public void continueCall(@NonNull String callId) {
    143           LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
    144         }
    145 
    146         @Override
    147         public void cancelCall(@NonNull String callId) {
    148           DialerCall call = CallList.getInstance().getCallById(callId);
    149           if (call == null) {
    150             LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
    151             return;
    152           }
    153           LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
    154           call.disconnect();
    155         }
    156       };
    157 
    158   public static void setIntentExtras(
    159       Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
    160     if (showDialpad) {
    161       intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
    162     }
    163     intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
    164     intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
    165   }
    166 
    167   public InCallActivityCommon(InCallActivity inCallActivity) {
    168     this.inCallActivity = inCallActivity;
    169   }
    170 
    171   public void onCreate(Bundle icicle) {
    172     // set this flag so this activity will stay in front of the keyguard
    173     // Have the WindowManager filter out touch events that are "too fat".
    174     int flags =
    175         WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    176             | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
    177             | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
    178 
    179     inCallActivity.getWindow().addFlags(flags);
    180 
    181     inCallActivity.setContentView(R.layout.incall_screen);
    182 
    183     internalResolveIntent(inCallActivity.getIntent());
    184 
    185     boolean isLandscape =
    186         inCallActivity.getResources().getConfiguration().orientation
    187             == Configuration.ORIENTATION_LANDSCAPE;
    188     boolean isRtl = ViewUtil.isRtl();
    189 
    190     if (isLandscape) {
    191       dialpadSlideInAnimation =
    192           AnimationUtils.loadAnimation(
    193               inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
    194       dialpadSlideOutAnimation =
    195           AnimationUtils.loadAnimation(
    196               inCallActivity,
    197               isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
    198     } else {
    199       dialpadSlideInAnimation =
    200           AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
    201       dialpadSlideOutAnimation =
    202           AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
    203     }
    204 
    205     dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
    206     dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
    207 
    208     dialpadSlideOutAnimation.setAnimationListener(
    209         new AnimationListenerAdapter() {
    210           @Override
    211           public void onAnimationEnd(Animation animation) {
    212             performHideDialpadFragment();
    213           }
    214         });
    215 
    216     if (icicle != null) {
    217       // If the dialpad was shown before, set variables indicating it should be shown and
    218       // populated with the previous DTMF text.  The dialpad is actually shown and populated
    219       // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
    220       if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
    221         boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
    222         showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
    223         animateDialpadOnShow = false;
    224       }
    225       dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
    226 
    227       SelectPhoneAccountDialogFragment dialogFragment =
    228           (SelectPhoneAccountDialogFragment)
    229               inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
    230       if (dialogFragment != null) {
    231         dialogFragment.setListener(selectAccountListener);
    232       }
    233     }
    234 
    235     InternationalCallOnWifiDialogFragment existingInternationalFragment =
    236         (InternationalCallOnWifiDialogFragment)
    237             inCallActivity
    238                 .getSupportFragmentManager()
    239                 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
    240     if (existingInternationalFragment != null) {
    241       LogUtil.i(
    242           "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
    243       existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
    244     }
    245 
    246     inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity);
    247   }
    248 
    249   public void onSaveInstanceState(Bundle out) {
    250     // TODO: The dialpad fragment should handle this as part of its own state
    251     out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
    252     DialpadFragment dialpadFragment = getDialpadFragment();
    253     if (dialpadFragment != null) {
    254       out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
    255     }
    256   }
    257 
    258   public void onStart() {
    259     // setting activity should be last thing in setup process
    260     InCallPresenter.getInstance().setActivity(inCallActivity);
    261     enableInCallOrientationEventListener(
    262         inCallActivity.getRequestedOrientation()
    263             == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
    264 
    265     InCallPresenter.getInstance().onActivityStarted();
    266   }
    267 
    268   public void onResume() {
    269     if (InCallPresenter.getInstance().isReadyForTearDown()) {
    270       LogUtil.i(
    271           "InCallActivityCommon.onResume",
    272           "InCallPresenter is ready for tear down, not sending updates");
    273     } else {
    274       updateTaskDescription();
    275       InCallPresenter.getInstance().onUiShowing(true);
    276     }
    277 
    278     // If there is a pending request to show or hide the dialpad, handle that now.
    279     if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
    280       if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
    281         // Exit fullscreen so that the user has access to the dialpad hide/show button and
    282         // can hide the dialpad.  Important when showing the dialpad from within dialer.
    283         InCallPresenter.getInstance().setFullScreen(false, true /* force */);
    284 
    285         inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
    286         animateDialpadOnShow = false;
    287 
    288         DialpadFragment dialpadFragment = getDialpadFragment();
    289         if (dialpadFragment != null) {
    290           dialpadFragment.setDtmfText(dtmfTextToPreopulate);
    291           dtmfTextToPreopulate = null;
    292         }
    293       } else {
    294         LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
    295         if (getDialpadFragment() != null) {
    296           inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
    297         }
    298       }
    299       showDialpadRequest = DIALPAD_REQUEST_NONE;
    300     }
    301 
    302     if (showPostCharWaitDialogOnResume) {
    303       showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
    304     }
    305 
    306     CallList.getInstance()
    307         .onInCallUiShown(
    308             inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
    309   }
    310 
    311   // onPause is guaranteed to be called when the InCallActivity goes
    312   // in the background.
    313   public void onPause() {
    314     DialpadFragment dialpadFragment = getDialpadFragment();
    315     if (dialpadFragment != null) {
    316       dialpadFragment.onDialerKeyUp(null);
    317     }
    318 
    319     InCallPresenter.getInstance().onUiShowing(false);
    320     if (inCallActivity.isFinishing()) {
    321       InCallPresenter.getInstance().unsetActivity(inCallActivity);
    322     }
    323   }
    324 
    325   public void onStop() {
    326     enableInCallOrientationEventListener(false);
    327     InCallPresenter.getInstance().updateIsChangingConfigurations();
    328     InCallPresenter.getInstance().onActivityStopped();
    329   }
    330 
    331   public void onDestroy() {
    332     InCallPresenter.getInstance().unsetActivity(inCallActivity);
    333     InCallPresenter.getInstance().updateIsChangingConfigurations();
    334   }
    335 
    336   void onNewIntent(Intent intent, boolean isRecreating) {
    337     LogUtil.i("InCallActivityCommon.onNewIntent", "");
    338 
    339     // We're being re-launched with a new Intent.  Since it's possible for a
    340     // single InCallActivity instance to persist indefinitely (even if we
    341     // finish() ourselves), this sequence can potentially happen any time
    342     // the InCallActivity needs to be displayed.
    343 
    344     // Stash away the new intent so that we can get it in the future
    345     // by calling getIntent().  (Otherwise getIntent() will return the
    346     // original Intent from when we first got created!)
    347     inCallActivity.setIntent(intent);
    348 
    349     // Activities are always paused before receiving a new intent, so
    350     // we can count on our onResume() method being called next.
    351 
    352     // Just like in onCreate(), handle the intent.
    353     // Skip if InCallActivity is going to recreate since this will be called in onCreate().
    354     if (!isRecreating) {
    355       internalResolveIntent(intent);
    356     }
    357   }
    358 
    359   public boolean onBackPressed(boolean isInCallScreenVisible) {
    360     LogUtil.i("InCallActivityCommon.onBackPressed", "");
    361 
    362     // BACK is also used to exit out of any "special modes" of the
    363     // in-call UI:
    364     if (!inCallActivity.isVisible()) {
    365       return true;
    366     }
    367 
    368     if (!isInCallScreenVisible) {
    369       return true;
    370     }
    371 
    372     DialpadFragment dialpadFragment = getDialpadFragment();
    373     if (dialpadFragment != null && dialpadFragment.isVisible()) {
    374       inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
    375       return true;
    376     }
    377 
    378     // Always disable the Back key while an incoming call is ringing
    379     DialerCall call = CallList.getInstance().getIncomingCall();
    380     if (call != null) {
    381       LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
    382       return true;
    383     }
    384 
    385     // Nothing special to do. Fall back to the default behavior.
    386     return false;
    387   }
    388 
    389   public boolean onKeyUp(int keyCode, KeyEvent event) {
    390     DialpadFragment dialpadFragment = getDialpadFragment();
    391     // push input to the dialer.
    392     if (dialpadFragment != null
    393         && (dialpadFragment.isVisible())
    394         && (dialpadFragment.onDialerKeyUp(event))) {
    395       return true;
    396     } else if (keyCode == KeyEvent.KEYCODE_CALL) {
    397       // Always consume CALL to be sure the PhoneWindow won't do anything with it
    398       return true;
    399     }
    400     return false;
    401   }
    402 
    403   public boolean onKeyDown(int keyCode, KeyEvent event) {
    404     switch (keyCode) {
    405       case KeyEvent.KEYCODE_CALL:
    406         boolean handled = InCallPresenter.getInstance().handleCallKey();
    407         if (!handled) {
    408           LogUtil.e(
    409               "InCallActivityCommon.onKeyDown",
    410               "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
    411         }
    412         // Always consume CALL to be sure the PhoneWindow won't do anything with it
    413         return true;
    414 
    415         // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
    416         // The standard system-wide handling of the ENDCALL key
    417         // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
    418         // already implements exactly what the UI spec wants,
    419         // namely (1) "hang up" if there's a current active call,
    420         // or (2) "don't answer" if there's a current ringing call.
    421 
    422       case KeyEvent.KEYCODE_CAMERA:
    423         // Disable the CAMERA button while in-call since it's too
    424         // easy to press accidentally.
    425         return true;
    426 
    427       case KeyEvent.KEYCODE_VOLUME_UP:
    428       case KeyEvent.KEYCODE_VOLUME_DOWN:
    429       case KeyEvent.KEYCODE_VOLUME_MUTE:
    430         // Ringer silencing handled by PhoneWindowManager.
    431         break;
    432 
    433       case KeyEvent.KEYCODE_MUTE:
    434         TelecomAdapter.getInstance()
    435             .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
    436         return true;
    437 
    438         // Various testing/debugging features, enabled ONLY when VERBOSE == true.
    439       case KeyEvent.KEYCODE_SLASH:
    440         if (LogUtil.isVerboseEnabled()) {
    441           LogUtil.v(
    442               "InCallActivityCommon.onKeyDown",
    443               "----------- InCallActivity View dump --------------");
    444           // Dump starting from the top-level view of the entire activity:
    445           Window w = inCallActivity.getWindow();
    446           View decorView = w.getDecorView();
    447           LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
    448           return true;
    449         }
    450         break;
    451       case KeyEvent.KEYCODE_EQUALS:
    452         break;
    453       default: // fall out
    454     }
    455 
    456     return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
    457   }
    458 
    459   private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
    460     LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
    461 
    462     // As soon as the user starts typing valid dialable keys on the
    463     // keyboard (presumably to type DTMF tones) we start passing the
    464     // key events to the DTMFDialer's onDialerKeyDown.
    465     DialpadFragment dialpadFragment = getDialpadFragment();
    466     if (dialpadFragment != null && dialpadFragment.isVisible()) {
    467       return dialpadFragment.onDialerKeyDown(event);
    468     }
    469 
    470     return false;
    471   }
    472 
    473   public void dismissKeyguard(boolean dismiss) {
    474     if (dismissKeyguard == dismiss) {
    475       return;
    476     }
    477     dismissKeyguard = dismiss;
    478     if (dismiss) {
    479       inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    480     } else {
    481       inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    482     }
    483   }
    484 
    485   public void showPostCharWaitDialog(String callId, String chars) {
    486     if (inCallActivity.isVisible()) {
    487       PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
    488       fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
    489 
    490       showPostCharWaitDialogOnResume = false;
    491       showPostCharWaitDialogCallId = null;
    492       showPostCharWaitDialogChars = null;
    493     } else {
    494       showPostCharWaitDialogOnResume = true;
    495       showPostCharWaitDialogCallId = callId;
    496       showPostCharWaitDialogChars = chars;
    497     }
    498   }
    499 
    500   public void maybeShowErrorDialogOnDisconnect(DisconnectCause cause) {
    501     LogUtil.i(
    502         "InCallActivityCommon.maybeShowErrorDialogOnDisconnect", "disconnect cause: %s", cause);
    503 
    504     if (!inCallActivity.isFinishing()) {
    505       if (EnableWifiCallingPrompt.shouldShowPrompt(cause)) {
    506         Pair<Dialog, CharSequence> pair =
    507             EnableWifiCallingPrompt.createDialog(inCallActivity, cause);
    508         showErrorDialog(pair.first, pair.second);
    509       } else if (shouldShowDisconnectErrorDialog(cause)) {
    510         Pair<Dialog, CharSequence> pair = getDisconnectErrorDialog(inCallActivity, cause);
    511         showErrorDialog(pair.first, pair.second);
    512       }
    513     }
    514   }
    515 
    516   /**
    517    * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
    518    * be shown on launch.
    519    *
    520    * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
    521    *     false} to indicate no change should be made to the dialpad visibility.
    522    */
    523   private void relaunchedFromDialer(boolean showDialpad) {
    524     showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
    525     animateDialpadOnShow = true;
    526 
    527     if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
    528       // If there's only one line in use, AND it's on hold, then we're sure the user
    529       // wants to use the dialpad toward the exact line, so un-hold the holding line.
    530       DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
    531       if (call != null && call.getState() == State.ONHOLD) {
    532         call.unhold();
    533       }
    534     }
    535   }
    536 
    537   void dismissPendingDialogs() {
    538     if (dialog != null) {
    539       dialog.dismiss();
    540       dialog = null;
    541     }
    542     if (selectPhoneAccountDialogFragment != null) {
    543       selectPhoneAccountDialogFragment.dismiss();
    544       selectPhoneAccountDialogFragment = null;
    545     }
    546 
    547     InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
    548         (InternationalCallOnWifiDialogFragment)
    549             inCallActivity
    550                 .getSupportFragmentManager()
    551                 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
    552     if (internationalCallOnWifiFragment != null) {
    553       LogUtil.i(
    554           "InCallActivityCommon.dismissPendingDialogs",
    555           "dismissing InternationalCallOnWifiDialogFragment");
    556       internationalCallOnWifiFragment.dismiss();
    557     }
    558   }
    559 
    560   private static boolean shouldShowDisconnectErrorDialog(@NonNull DisconnectCause cause) {
    561     return !TextUtils.isEmpty(cause.getDescription())
    562         && (cause.getCode() == DisconnectCause.ERROR
    563             || cause.getCode() == DisconnectCause.RESTRICTED);
    564   }
    565 
    566   private static Pair<Dialog, CharSequence> getDisconnectErrorDialog(
    567       @NonNull Context context, @NonNull DisconnectCause cause) {
    568     CharSequence message = cause.getDescription();
    569     Dialog dialog =
    570         new AlertDialog.Builder(context)
    571             .setMessage(message)
    572             .setPositiveButton(android.R.string.ok, null)
    573             .create();
    574     return new Pair<>(dialog, message);
    575   }
    576 
    577   private void showErrorDialog(Dialog dialog, CharSequence message) {
    578     LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
    579     inCallActivity.dismissPendingDialogs();
    580 
    581     // Show toast if apps is in background when dialog won't be visible.
    582     if (!inCallActivity.isVisible()) {
    583       Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
    584       return;
    585     }
    586 
    587     this.dialog = dialog;
    588     dialog.setOnDismissListener(
    589         new OnDismissListener() {
    590           @Override
    591           public void onDismiss(DialogInterface dialog) {
    592             LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
    593             onDialogDismissed();
    594           }
    595         });
    596     dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    597     dialog.show();
    598   }
    599 
    600   private void onDialogDismissed() {
    601     dialog = null;
    602     CallList.getInstance().onErrorDialogDismissed();
    603     InCallPresenter.getInstance().onDismissDialog();
    604   }
    605 
    606   public void enableInCallOrientationEventListener(boolean enable) {
    607     if (enable) {
    608       inCallOrientationEventListener.enable(true);
    609     } else {
    610       inCallOrientationEventListener.disable();
    611     }
    612   }
    613 
    614   public void setExcludeFromRecents(boolean exclude) {
    615     List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
    616     int taskId = inCallActivity.getTaskId();
    617     for (int i = 0; i < tasks.size(); i++) {
    618       ActivityManager.AppTask task = tasks.get(i);
    619       try {
    620         if (task.getTaskInfo().id == taskId) {
    621           task.setExcludeFromRecents(exclude);
    622         }
    623       } catch (RuntimeException e) {
    624         LogUtil.e(
    625             "InCallActivityCommon.setExcludeFromRecents",
    626             "RuntimeException when excluding task from recents.",
    627             e);
    628       }
    629     }
    630   }
    631 
    632   void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
    633     LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
    634     if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
    635       LogUtil.i(
    636           "InCallActivityCommon.showInternationalCallOnWifiDialog",
    637           "InternationalCallOnWifiDialogFragment.shouldShow returned false");
    638       return;
    639     }
    640 
    641     InternationalCallOnWifiDialogFragment fragment =
    642         InternationalCallOnWifiDialogFragment.newInstance(
    643             call.getId(), internationalCallOnWifiCallback);
    644     fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
    645   }
    646 
    647   public void showWifiToLteHandoverToast(DialerCall call) {
    648     if (call.hasShownWiFiToLteHandoverToast()) {
    649       return;
    650     }
    651     Toast.makeText(
    652             inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
    653         .show();
    654     call.setHasShownWiFiToLteHandoverToast();
    655   }
    656 
    657   public void showWifiFailedDialog(final DialerCall call) {
    658     if (call.showWifiHandoverAlertAsToast()) {
    659       LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
    660       Toast.makeText(
    661               inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
    662           .show();
    663       return;
    664     }
    665 
    666     dismissPendingDialogs();
    667 
    668     AlertDialog.Builder builder =
    669         new AlertDialog.Builder(inCallActivity)
    670             .setTitle(R.string.video_call_lte_to_wifi_failed_title);
    671 
    672     // This allows us to use the theme of the dialog instead of the activity
    673     View dialogCheckBoxView =
    674         View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
    675     final CheckBox wifiHandoverFailureCheckbox =
    676         (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
    677     wifiHandoverFailureCheckbox.setChecked(false);
    678 
    679     dialog =
    680         builder
    681             .setView(dialogCheckBoxView)
    682             .setMessage(R.string.video_call_lte_to_wifi_failed_message)
    683             .setOnCancelListener(
    684                 new OnCancelListener() {
    685                   @Override
    686                   public void onCancel(DialogInterface dialog) {
    687                     onDialogDismissed();
    688                   }
    689                 })
    690             .setPositiveButton(
    691                 android.R.string.ok,
    692                 new DialogInterface.OnClickListener() {
    693                   @Override
    694                   public void onClick(DialogInterface dialog, int id) {
    695                     call.setDoNotShowDialogForHandoffToWifiFailure(
    696                         wifiHandoverFailureCheckbox.isChecked());
    697                     dialog.cancel();
    698                     onDialogDismissed();
    699                   }
    700                 })
    701             .create();
    702 
    703     LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
    704     dialog.show();
    705   }
    706 
    707   public boolean showDialpadFragment(boolean show, boolean animate) {
    708     // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
    709     boolean isDialpadVisible = isDialpadVisible();
    710     LogUtil.i(
    711         "InCallActivityCommon.showDialpadFragment",
    712         "show: %b, animate: %b, " + "isDialpadVisible: %b",
    713         show,
    714         animate,
    715         isDialpadVisible);
    716     if (show == isDialpadVisible) {
    717       return false;
    718     }
    719 
    720     FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
    721     if (dialpadFragmentManager == null) {
    722       LogUtil.i(
    723           "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
    724       return false;
    725     }
    726 
    727     // We don't do a FragmentTransaction on the hide case because it will be dealt with when
    728     // the listener is fired after an animation finishes.
    729     if (!animate) {
    730       if (show) {
    731         performShowDialpadFragment(dialpadFragmentManager);
    732       } else {
    733         performHideDialpadFragment();
    734       }
    735     } else {
    736       if (show) {
    737         performShowDialpadFragment(dialpadFragmentManager);
    738         getDialpadFragment().animateShowDialpad();
    739       }
    740       getDialpadFragment()
    741           .getView()
    742           .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
    743     }
    744 
    745     ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
    746     if (sensor != null) {
    747       sensor.onDialpadVisible(show);
    748     }
    749     showDialpadRequest = DIALPAD_REQUEST_NONE;
    750     return true;
    751   }
    752 
    753   private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
    754     FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
    755     DialpadFragment dialpadFragment = getDialpadFragment();
    756     if (dialpadFragment == null) {
    757       transaction.add(
    758           inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
    759     } else {
    760       transaction.show(dialpadFragment);
    761     }
    762 
    763     transaction.commitAllowingStateLoss();
    764     dialpadFragmentManager.executePendingTransactions();
    765 
    766     Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
    767   }
    768 
    769   private void performHideDialpadFragment() {
    770     FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
    771     if (fragmentManager == null) {
    772       LogUtil.e(
    773           "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
    774       return;
    775     }
    776 
    777     Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
    778     if (fragment != null) {
    779       FragmentTransaction transaction = fragmentManager.beginTransaction();
    780       transaction.hide(fragment);
    781       transaction.commitAllowingStateLoss();
    782       fragmentManager.executePendingTransactions();
    783     }
    784   }
    785 
    786   public boolean isDialpadVisible() {
    787     DialpadFragment dialpadFragment = getDialpadFragment();
    788     return dialpadFragment != null && dialpadFragment.isVisible();
    789   }
    790 
    791   /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
    792   @Nullable
    793   private DialpadFragment getDialpadFragment() {
    794     FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
    795     if (fragmentManager == null) {
    796       return null;
    797     }
    798     return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
    799   }
    800 
    801   public void updateTaskDescription() {
    802     Resources resources = inCallActivity.getResources();
    803     int color;
    804     if (resources.getBoolean(R.bool.is_layout_landscape)) {
    805       color =
    806           ResourcesCompat.getColor(
    807               resources, R.color.statusbar_background_color, inCallActivity.getTheme());
    808     } else {
    809       color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
    810     }
    811 
    812     TaskDescription td =
    813         new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
    814     inCallActivity.setTaskDescription(td);
    815   }
    816 
    817   public boolean hasPendingDialogs() {
    818     return dialog != null;
    819   }
    820 
    821   private void internalResolveIntent(Intent intent) {
    822     if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
    823       return;
    824     }
    825 
    826     if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
    827       // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
    828       // dialpad should be initially visible.  If the extra isn't
    829       // present at all, we just leave the dialpad in its previous state.
    830       boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
    831       LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
    832 
    833       relaunchedFromDialer(showDialpad);
    834     }
    835 
    836     DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
    837     if (outgoingCall == null) {
    838       outgoingCall = CallList.getInstance().getPendingOutgoingCall();
    839     }
    840 
    841     if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
    842       intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
    843 
    844       // InCallActivity is responsible for disconnecting a new outgoing call if there
    845       // is no way of making it (i.e. no valid call capable accounts).
    846       // If the version is not MSIM compatible, then ignore this code.
    847       if (CompatUtils.isMSIMCompatible()
    848           && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
    849         LogUtil.i(
    850             "InCallActivityCommon.internalResolveIntent",
    851             "call with no valid accounts, disconnecting");
    852         outgoingCall.disconnect();
    853       }
    854 
    855       dismissKeyguard(true);
    856     }
    857 
    858     boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
    859     if (didShowAccountSelectionDialog) {
    860       inCallActivity.hideMainInCallFragment();
    861     }
    862   }
    863 
    864   private boolean maybeShowAccountSelectionDialog() {
    865     DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
    866     if (waitingForAccountCall == null) {
    867       return false;
    868     }
    869 
    870     Bundle extras = waitingForAccountCall.getIntentExtras();
    871     List<PhoneAccountHandle> phoneAccountHandles;
    872     if (extras != null) {
    873       phoneAccountHandles =
    874           extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
    875     } else {
    876       phoneAccountHandles = new ArrayList<>();
    877     }
    878 
    879     selectPhoneAccountDialogFragment =
    880         SelectPhoneAccountDialogFragment.newInstance(
    881             R.string.select_phone_account_for_calls,
    882             true,
    883             phoneAccountHandles,
    884             selectAccountListener,
    885             waitingForAccountCall.getId());
    886     selectPhoneAccountDialogFragment.show(
    887         inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
    888     return true;
    889   }
    890 }
    891