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