Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2006 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.ActionBar;
     20 import android.app.Activity;
     21 import android.app.ActivityManager;
     22 import android.app.AlertDialog;
     23 import android.app.DialogFragment;
     24 import android.app.Fragment;
     25 import android.app.FragmentManager;
     26 import android.app.FragmentTransaction;
     27 import android.content.Context;
     28 import android.content.DialogInterface;
     29 import android.content.DialogInterface.OnCancelListener;
     30 import android.content.DialogInterface.OnClickListener;
     31 import android.content.Intent;
     32 import android.content.res.Configuration;
     33 import android.graphics.Point;
     34 import android.hardware.SensorManager;
     35 import android.os.Bundle;
     36 import android.os.Trace;
     37 import android.telecom.DisconnectCause;
     38 import android.telecom.PhoneAccountHandle;
     39 import android.text.TextUtils;
     40 import android.view.KeyEvent;
     41 import android.view.MenuItem;
     42 import android.view.MotionEvent;
     43 import android.view.OrientationEventListener;
     44 import android.view.Surface;
     45 import android.view.View;
     46 import android.view.View.OnTouchListener;
     47 import android.view.Window;
     48 import android.view.WindowManager;
     49 import android.view.accessibility.AccessibilityEvent;
     50 import android.view.animation.Animation;
     51 import android.view.animation.AnimationUtils;
     52 
     53 import com.android.contacts.common.activity.TransactionSafeActivity;
     54 import com.android.contacts.common.compat.CompatUtils;
     55 import com.android.contacts.common.interactions.TouchPointManager;
     56 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
     57 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
     58 import com.android.dialer.R;
     59 import com.android.dialer.logging.Logger;
     60 import com.android.dialer.logging.ScreenEvent;
     61 import com.android.incallui.Call.State;
     62 import com.android.incallui.util.AccessibilityUtil;
     63 import com.android.phone.common.animation.AnimUtils;
     64 import com.android.phone.common.animation.AnimationListenerAdapter;
     65 
     66 import java.util.ArrayList;
     67 import java.util.List;
     68 import java.util.Locale;
     69 
     70 /**
     71  * Main activity that the user interacts with while in a live call.
     72  */
     73 public class InCallActivity extends TransactionSafeActivity implements FragmentDisplayManager {
     74 
     75     public static final String TAG = InCallActivity.class.getSimpleName();
     76 
     77     public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
     78     public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
     79     public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
     80 
     81     private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
     82     private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment";
     83     private static final String TAG_CALLCARD_FRAGMENT = "tag_callcard_fragment";
     84     private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment";
     85     private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
     86 
     87     private static final int DIALPAD_REQUEST_NONE = 1;
     88     private static final int DIALPAD_REQUEST_SHOW = 2;
     89     private static final int DIALPAD_REQUEST_HIDE = 3;
     90 
     91     /**
     92      * This is used to relaunch the activity if resizing beyond which it needs to load different
     93      * layout file.
     94      */
     95     private static final int SCREEN_HEIGHT_RESIZE_THRESHOLD = 500;
     96 
     97     private CallButtonFragment mCallButtonFragment;
     98     private CallCardFragment mCallCardFragment;
     99     private AnswerFragment mAnswerFragment;
    100     private DialpadFragment mDialpadFragment;
    101     private ConferenceManagerFragment mConferenceManagerFragment;
    102     private FragmentManager mChildFragmentManager;
    103 
    104     private AlertDialog mDialog;
    105     private InCallOrientationEventListener mInCallOrientationEventListener;
    106 
    107     /**
    108      * Used to indicate whether the dialpad should be hidden or shown {@link #onResume}.
    109      * {@code #DIALPAD_REQUEST_SHOW} indicates that the dialpad should be shown.
    110      * {@code #DIALPAD_REQUEST_HIDE} indicates that the dialpad should be hidden.
    111      * {@code #DIALPAD_REQUEST_NONE} indicates no change should be made to dialpad visibility.
    112      */
    113     private int mShowDialpadRequest = DIALPAD_REQUEST_NONE;
    114 
    115     /**
    116      * Use to determine if the dialpad should be animated on show.
    117      */
    118     private boolean mAnimateDialpadOnShow;
    119 
    120     /**
    121      * Use to determine the DTMF Text which should be pre-populated in the dialpad.
    122      */
    123     private String mDtmfText;
    124 
    125     /**
    126      * Use to pass parameters for showing the PostCharDialog to {@link #onResume}
    127      */
    128     private boolean mShowPostCharWaitDialogOnResume;
    129     private String mShowPostCharWaitDialogCallId;
    130     private String mShowPostCharWaitDialogChars;
    131 
    132     private boolean mIsLandscape;
    133     private Animation mSlideIn;
    134     private Animation mSlideOut;
    135     private boolean mDismissKeyguard = false;
    136 
    137     AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
    138         @Override
    139         public void onAnimationEnd(Animation animation) {
    140             showFragment(TAG_DIALPAD_FRAGMENT, false, true);
    141         }
    142     };
    143 
    144     private OnTouchListener mDispatchTouchEventListener;
    145 
    146     private SelectPhoneAccountListener mSelectAcctListener = new SelectPhoneAccountListener() {
    147         @Override
    148         public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
    149                 boolean setDefault) {
    150             InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle,
    151                     setDefault);
    152         }
    153 
    154         @Override
    155         public void onDialogDismissed() {
    156             InCallPresenter.getInstance().cancelAccountSelection();
    157         }
    158     };
    159 
    160     @Override
    161     protected void onCreate(Bundle icicle) {
    162         Log.d(this, "onCreate()...  this = " + this);
    163 
    164         super.onCreate(icicle);
    165 
    166         // set this flag so this activity will stay in front of the keyguard
    167         // Have the WindowManager filter out touch events that are "too fat".
    168         int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    169                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
    170                 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
    171 
    172         getWindow().addFlags(flags);
    173 
    174         // Setup action bar for the conference call manager.
    175         requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
    176         ActionBar actionBar = getActionBar();
    177         if (actionBar != null) {
    178             actionBar.setDisplayHomeAsUpEnabled(true);
    179             actionBar.setDisplayShowTitleEnabled(true);
    180             actionBar.hide();
    181         }
    182 
    183         // TODO(klp): Do we need to add this back when prox sensor is not available?
    184         // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
    185 
    186         setContentView(R.layout.incall_screen);
    187 
    188         internalResolveIntent(getIntent());
    189 
    190         mIsLandscape = getResources().getConfiguration().orientation ==
    191                 Configuration.ORIENTATION_LANDSCAPE;
    192 
    193         final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
    194                 View.LAYOUT_DIRECTION_RTL;
    195 
    196         if (mIsLandscape) {
    197             mSlideIn = AnimationUtils.loadAnimation(this,
    198                     isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
    199             mSlideOut = AnimationUtils.loadAnimation(this,
    200                     isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
    201         } else {
    202             mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
    203             mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
    204         }
    205 
    206         mSlideIn.setInterpolator(AnimUtils.EASE_IN);
    207         mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
    208 
    209         mSlideOut.setAnimationListener(mSlideOutListener);
    210 
    211         // If the dialpad fragment already exists, retrieve it.  This is important when rotating as
    212         // we will not be able to hide or show the dialpad after the rotation otherwise.
    213         Fragment existingFragment =
    214                 getFragmentManager().findFragmentByTag(DialpadFragment.class.getName());
    215         if (existingFragment != null) {
    216             mDialpadFragment = (DialpadFragment) existingFragment;
    217         }
    218 
    219         if (icicle != null) {
    220             // If the dialpad was shown before, set variables indicating it should be shown and
    221             // populated with the previous DTMF text.  The dialpad is actually shown and populated
    222             // in onResume() to ensure the hosting CallCardFragment has been inflated and is ready
    223             // to receive it.
    224             if (icicle.containsKey(SHOW_DIALPAD_EXTRA)) {
    225                 boolean showDialpad = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
    226                 mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
    227                 mAnimateDialpadOnShow = false;
    228             }
    229             mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
    230 
    231             SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
    232                     getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
    233             if (dialogFragment != null) {
    234                 dialogFragment.setListener(mSelectAcctListener);
    235             }
    236         }
    237         mInCallOrientationEventListener = new InCallOrientationEventListener(this);
    238 
    239         Log.d(this, "onCreate(): exit");
    240     }
    241 
    242     @Override
    243     protected void onSaveInstanceState(Bundle out) {
    244         // TODO: The dialpad fragment should handle this as part of its own state
    245         out.putBoolean(SHOW_DIALPAD_EXTRA,
    246                 mCallButtonFragment != null && mCallButtonFragment.isDialpadVisible());
    247         if (mDialpadFragment != null) {
    248             out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText());
    249         }
    250         super.onSaveInstanceState(out);
    251     }
    252 
    253     @Override
    254     protected void onStart() {
    255         Log.d(this, "onStart()...");
    256         super.onStart();
    257 
    258         // setting activity should be last thing in setup process
    259         InCallPresenter.getInstance().setActivity(this);
    260         enableInCallOrientationEventListener(getRequestedOrientation() ==
    261                 InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
    262 
    263         InCallPresenter.getInstance().onActivityStarted();
    264     }
    265 
    266     @Override
    267     protected void onResume() {
    268         Log.i(this, "onResume()...");
    269         super.onResume();
    270 
    271         InCallPresenter.getInstance().setThemeColors();
    272         InCallPresenter.getInstance().onUiShowing(true);
    273 
    274         // Clear fullscreen state onResume; the stored value may not match reality.
    275         InCallPresenter.getInstance().clearFullscreen();
    276 
    277         // If there is a pending request to show or hide the dialpad, handle that now.
    278         if (mShowDialpadRequest != DIALPAD_REQUEST_NONE) {
    279             if (mShowDialpadRequest == DIALPAD_REQUEST_SHOW) {
    280                 // Exit fullscreen so that the user has access to the dialpad hide/show button and
    281                 // can hide the dialpad.  Important when showing the dialpad from within dialer.
    282                 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
    283 
    284                 mCallButtonFragment.displayDialpad(true /* show */,
    285                         mAnimateDialpadOnShow /* animate */);
    286                 mAnimateDialpadOnShow = false;
    287 
    288                 if (mDialpadFragment != null) {
    289                     mDialpadFragment.setDtmfText(mDtmfText);
    290                     mDtmfText = null;
    291                 }
    292             } else {
    293                 Log.v(this, "onResume : force hide dialpad");
    294                 if (mDialpadFragment != null) {
    295                     mCallButtonFragment.displayDialpad(false /* show */, false /* animate */);
    296                 }
    297             }
    298             mShowDialpadRequest = DIALPAD_REQUEST_NONE;
    299         }
    300 
    301         if (mShowPostCharWaitDialogOnResume) {
    302             showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
    303         }
    304     }
    305 
    306     // onPause is guaranteed to be called when the InCallActivity goes
    307     // in the background.
    308     @Override
    309     protected void onPause() {
    310         Log.d(this, "onPause()...");
    311         if (mDialpadFragment != null) {
    312             mDialpadFragment.onDialerKeyUp(null);
    313         }
    314 
    315         InCallPresenter.getInstance().onUiShowing(false);
    316         if (isFinishing()) {
    317             InCallPresenter.getInstance().unsetActivity(this);
    318         }
    319         super.onPause();
    320     }
    321 
    322     @Override
    323     protected void onStop() {
    324         Log.d(this, "onStop()...");
    325         enableInCallOrientationEventListener(false);
    326         InCallPresenter.getInstance().updateIsChangingConfigurations();
    327         InCallPresenter.getInstance().onActivityStopped();
    328         super.onStop();
    329     }
    330 
    331     @Override
    332     protected void onDestroy() {
    333         Log.d(this, "onDestroy()...  this = " + this);
    334         InCallPresenter.getInstance().unsetActivity(this);
    335         InCallPresenter.getInstance().updateIsChangingConfigurations();
    336         super.onDestroy();
    337     }
    338 
    339     /**
    340      * When fragments have a parent fragment, onAttachFragment is not called on the parent
    341      * activity. To fix this, register our own callback instead that is always called for
    342      * all fragments.
    343      *
    344      * @see {@link BaseFragment#onAttach(Activity)}
    345      */
    346     @Override
    347     public void onFragmentAttached(Fragment fragment) {
    348         if (fragment instanceof DialpadFragment) {
    349             mDialpadFragment = (DialpadFragment) fragment;
    350         } else if (fragment instanceof AnswerFragment) {
    351             mAnswerFragment = (AnswerFragment) fragment;
    352         } else if (fragment instanceof CallCardFragment) {
    353             mCallCardFragment = (CallCardFragment) fragment;
    354             mChildFragmentManager = mCallCardFragment.getChildFragmentManager();
    355         } else if (fragment instanceof ConferenceManagerFragment) {
    356             mConferenceManagerFragment = (ConferenceManagerFragment) fragment;
    357         } else if (fragment instanceof CallButtonFragment) {
    358             mCallButtonFragment = (CallButtonFragment) fragment;
    359         }
    360     }
    361 
    362     @Override
    363     public void onConfigurationChanged(Configuration newConfig) {
    364         super.onConfigurationChanged(newConfig);
    365         Configuration oldConfig = getResources().getConfiguration();
    366         Log.v(this, String.format(
    367                 "incallui config changed, screen size: w%ddp x h%ddp old:w%ddp x h%ddp",
    368                 newConfig.screenWidthDp, newConfig.screenHeightDp,
    369                 oldConfig.screenWidthDp, oldConfig.screenHeightDp));
    370         // Recreate this activity if height is changing beyond the threshold to load different
    371         // layout file.
    372         if (oldConfig.screenHeightDp < SCREEN_HEIGHT_RESIZE_THRESHOLD &&
    373                 newConfig.screenHeightDp > SCREEN_HEIGHT_RESIZE_THRESHOLD ||
    374                 oldConfig.screenHeightDp > SCREEN_HEIGHT_RESIZE_THRESHOLD &&
    375                         newConfig.screenHeightDp < SCREEN_HEIGHT_RESIZE_THRESHOLD) {
    376             Log.i(this, String.format(
    377                     "Recreate activity due to resize beyond threshold: %d dp",
    378                     SCREEN_HEIGHT_RESIZE_THRESHOLD));
    379             recreate();
    380         }
    381     }
    382 
    383     /**
    384      * Returns true when the Activity is currently visible.
    385      */
    386     /* package */ boolean isVisible() {
    387         return isSafeToCommitTransactions();
    388     }
    389 
    390     private boolean hasPendingDialogs() {
    391         return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs());
    392     }
    393 
    394     @Override
    395     public void finish() {
    396         Log.i(this, "finish().  Dialog showing: " + (mDialog != null));
    397 
    398         // skip finish if we are still showing a dialog.
    399         if (!hasPendingDialogs()) {
    400             super.finish();
    401         }
    402     }
    403 
    404     @Override
    405     protected void onNewIntent(Intent intent) {
    406         Log.d(this, "onNewIntent: intent = " + intent);
    407 
    408         // We're being re-launched with a new Intent.  Since it's possible for a
    409         // single InCallActivity instance to persist indefinitely (even if we
    410         // finish() ourselves), this sequence can potentially happen any time
    411         // the InCallActivity needs to be displayed.
    412 
    413         // Stash away the new intent so that we can get it in the future
    414         // by calling getIntent().  (Otherwise getIntent() will return the
    415         // original Intent from when we first got created!)
    416         setIntent(intent);
    417 
    418         // Activities are always paused before receiving a new intent, so
    419         // we can count on our onResume() method being called next.
    420 
    421         // Just like in onCreate(), handle the intent.
    422         internalResolveIntent(intent);
    423     }
    424 
    425     @Override
    426     public void onBackPressed() {
    427         Log.i(this, "onBackPressed");
    428 
    429         // BACK is also used to exit out of any "special modes" of the
    430         // in-call UI:
    431         if (!isVisible()) {
    432             return;
    433         }
    434 
    435         if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible())
    436                 && (mCallCardFragment == null || !mCallCardFragment.isVisible())) {
    437             return;
    438         }
    439 
    440         if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
    441             mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
    442             return;
    443         } else if (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible()) {
    444             showConferenceFragment(false);
    445             return;
    446         }
    447 
    448         // Always disable the Back key while an incoming call is ringing
    449         final Call call = CallList.getInstance().getIncomingCall();
    450         if (call != null) {
    451             Log.i(this, "Consume Back press for an incoming call");
    452             return;
    453         }
    454 
    455         // Nothing special to do.  Fall back to the default behavior.
    456         super.onBackPressed();
    457     }
    458 
    459     @Override
    460     public boolean onOptionsItemSelected(MenuItem item) {
    461         final int itemId = item.getItemId();
    462         if (itemId == android.R.id.home) {
    463             onBackPressed();
    464             return true;
    465         }
    466         return super.onOptionsItemSelected(item);
    467     }
    468 
    469     @Override
    470     public boolean onKeyUp(int keyCode, KeyEvent event) {
    471         // push input to the dialer.
    472         if (mDialpadFragment != null && (mDialpadFragment.isVisible()) &&
    473                 (mDialpadFragment.onDialerKeyUp(event))) {
    474             return true;
    475         } else if (keyCode == KeyEvent.KEYCODE_CALL) {
    476             // Always consume CALL to be sure the PhoneWindow won't do anything with it
    477             return true;
    478         }
    479         return super.onKeyUp(keyCode, event);
    480     }
    481 
    482     @Override
    483     public boolean dispatchTouchEvent(MotionEvent ev) {
    484         if (mDispatchTouchEventListener != null) {
    485             boolean handled = mDispatchTouchEventListener.onTouch(null, ev);
    486             if (handled) {
    487                 return true;
    488             }
    489         }
    490         return super.dispatchTouchEvent(ev);
    491     }
    492 
    493     @Override
    494     public boolean onKeyDown(int keyCode, KeyEvent event) {
    495         switch (keyCode) {
    496             case KeyEvent.KEYCODE_CALL:
    497                 boolean handled = InCallPresenter.getInstance().handleCallKey();
    498                 if (!handled) {
    499                     Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown");
    500                 }
    501                 // Always consume CALL to be sure the PhoneWindow won't do anything with it
    502                 return true;
    503 
    504             // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
    505             // The standard system-wide handling of the ENDCALL key
    506             // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
    507             // already implements exactly what the UI spec wants,
    508             // namely (1) "hang up" if there's a current active call,
    509             // or (2) "don't answer" if there's a current ringing call.
    510 
    511             case KeyEvent.KEYCODE_CAMERA:
    512                 // Disable the CAMERA button while in-call since it's too
    513                 // easy to press accidentally.
    514                 return true;
    515 
    516             case KeyEvent.KEYCODE_VOLUME_UP:
    517             case KeyEvent.KEYCODE_VOLUME_DOWN:
    518             case KeyEvent.KEYCODE_VOLUME_MUTE:
    519                 // Ringer silencing handled by PhoneWindowManager.
    520                 break;
    521 
    522             case KeyEvent.KEYCODE_MUTE:
    523                 // toggle mute
    524                 TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute());
    525                 return true;
    526 
    527             // Various testing/debugging features, enabled ONLY when VERBOSE == true.
    528             case KeyEvent.KEYCODE_SLASH:
    529                 if (Log.VERBOSE) {
    530                     Log.v(this, "----------- InCallActivity View dump --------------");
    531                     // Dump starting from the top-level view of the entire activity:
    532                     Window w = this.getWindow();
    533                     View decorView = w.getDecorView();
    534                     Log.d(this, "View dump:" + decorView);
    535                     return true;
    536                 }
    537                 break;
    538             case KeyEvent.KEYCODE_EQUALS:
    539                 // TODO: Dump phone state?
    540                 break;
    541         }
    542 
    543         if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
    544             return true;
    545         }
    546         return super.onKeyDown(keyCode, event);
    547     }
    548 
    549     private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
    550         Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
    551 
    552         // As soon as the user starts typing valid dialable keys on the
    553         // keyboard (presumably to type DTMF tones) we start passing the
    554         // key events to the DTMFDialer's onDialerKeyDown.
    555         if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
    556             return mDialpadFragment.onDialerKeyDown(event);
    557         }
    558 
    559         return false;
    560     }
    561 
    562     public CallButtonFragment getCallButtonFragment() {
    563         return mCallButtonFragment;
    564     }
    565 
    566     public CallCardFragment getCallCardFragment() {
    567         return mCallCardFragment;
    568     }
    569 
    570     public AnswerFragment getAnswerFragment() {
    571         return mAnswerFragment;
    572     }
    573 
    574     private void internalResolveIntent(Intent intent) {
    575         final String action = intent.getAction();
    576         if (action.equals(Intent.ACTION_MAIN)) {
    577             // This action is the normal way to bring up the in-call UI.
    578             //
    579             // But we do check here for one extra that can come along with the
    580             // ACTION_MAIN intent:
    581 
    582             if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
    583                 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
    584                 // dialpad should be initially visible.  If the extra isn't
    585                 // present at all, we just leave the dialpad in its previous state.
    586 
    587                 final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
    588                 Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
    589 
    590                 relaunchedFromDialer(showDialpad);
    591             }
    592 
    593             boolean newOutgoingCall = false;
    594             if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) {
    595                 intent.removeExtra(NEW_OUTGOING_CALL_EXTRA);
    596                 Call call = CallList.getInstance().getOutgoingCall();
    597                 if (call == null) {
    598                     call = CallList.getInstance().getPendingOutgoingCall();
    599                 }
    600 
    601                 Bundle extras = null;
    602                 if (call != null) {
    603                     extras = call.getTelecomCall().getDetails().getIntentExtras();
    604                 }
    605                 if (extras == null) {
    606                     // Initialize the extras bundle to avoid NPE
    607                     extras = new Bundle();
    608                 }
    609 
    610                 Point touchPoint = null;
    611                 if (TouchPointManager.getInstance().hasValidPoint()) {
    612                     // Use the most immediate touch point in the InCallUi if available
    613                     touchPoint = TouchPointManager.getInstance().getPoint();
    614                 } else {
    615                     // Otherwise retrieve the touch point from the call intent
    616                     if (call != null) {
    617                         touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT);
    618                     }
    619                 }
    620 
    621                 // Start animation for new outgoing call
    622                 CircularRevealFragment.startCircularReveal(getFragmentManager(), touchPoint,
    623                         InCallPresenter.getInstance());
    624 
    625                 // InCallActivity is responsible for disconnecting a new outgoing call if there
    626                 // is no way of making it (i.e. no valid call capable accounts).
    627                 // If the version is not MSIM compatible, then ignore this code.
    628                 if (CompatUtils.isMSIMCompatible()
    629                         && InCallPresenter.isCallWithNoValidAccounts(call)) {
    630                     TelecomAdapter.getInstance().disconnectCall(call.getId());
    631                 }
    632 
    633                 dismissKeyguard(true);
    634                 newOutgoingCall = true;
    635             }
    636 
    637             Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall();
    638             if (pendingAccountSelectionCall != null) {
    639                 showCallCardFragment(false);
    640                 Bundle extras =
    641                         pendingAccountSelectionCall.getTelecomCall().getDetails().getIntentExtras();
    642 
    643                 final List<PhoneAccountHandle> phoneAccountHandles;
    644                 if (extras != null) {
    645                     phoneAccountHandles = extras.getParcelableArrayList(
    646                             android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
    647                 } else {
    648                     phoneAccountHandles = new ArrayList<>();
    649                 }
    650 
    651                 DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
    652                         R.string.select_phone_account_for_calls, true, phoneAccountHandles,
    653                         mSelectAcctListener);
    654                 dialogFragment.show(getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT);
    655             } else if (!newOutgoingCall) {
    656                 showCallCardFragment(true);
    657             }
    658             return;
    659         }
    660     }
    661 
    662     /**
    663      * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad
    664      * should be shown on launch.
    665      *
    666      * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and
    667      *                                {@code false} to indicate no change should be made to the
    668      *                                dialpad visibility.
    669      */
    670     private void relaunchedFromDialer(boolean showDialpad) {
    671         mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
    672         mAnimateDialpadOnShow = true;
    673 
    674         if (mShowDialpadRequest == DIALPAD_REQUEST_SHOW) {
    675             // If there's only one line in use, AND it's on hold, then we're sure the user
    676             // wants to use the dialpad toward the exact line, so un-hold the holding line.
    677             final Call call = CallList.getInstance().getActiveOrBackgroundCall();
    678             if (call != null && call.getState() == State.ONHOLD) {
    679                 TelecomAdapter.getInstance().unholdCall(call.getId());
    680             }
    681         }
    682     }
    683 
    684     public void dismissKeyguard(boolean dismiss) {
    685         if (mDismissKeyguard == dismiss) {
    686             return;
    687         }
    688         mDismissKeyguard = dismiss;
    689         if (dismiss) {
    690             getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    691         } else {
    692             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    693         }
    694     }
    695 
    696     private void showFragment(String tag, boolean show, boolean executeImmediately) {
    697         Trace.beginSection("showFragment - " + tag);
    698         final FragmentManager fm = getFragmentManagerForTag(tag);
    699 
    700         if (fm == null) {
    701             Log.w(TAG, "Fragment manager is null for : " + tag);
    702             return;
    703         }
    704 
    705         Fragment fragment = fm.findFragmentByTag(tag);
    706         if (!show && fragment == null) {
    707             // Nothing to show, so bail early.
    708             return;
    709         }
    710 
    711         final FragmentTransaction transaction = fm.beginTransaction();
    712         if (show) {
    713             if (fragment == null) {
    714                 fragment = createNewFragmentForTag(tag);
    715                 transaction.add(getContainerIdForFragment(tag), fragment, tag);
    716             } else {
    717                 transaction.show(fragment);
    718             }
    719             Logger.logScreenView(getScreenTypeForTag(tag), this);
    720         } else {
    721             transaction.hide(fragment);
    722         }
    723 
    724         transaction.commitAllowingStateLoss();
    725         if (executeImmediately) {
    726             fm.executePendingTransactions();
    727         }
    728         Trace.endSection();
    729     }
    730 
    731     private Fragment createNewFragmentForTag(String tag) {
    732         if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
    733             mDialpadFragment = new DialpadFragment();
    734             return mDialpadFragment;
    735         } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
    736             if (AccessibilityUtil.isTalkBackEnabled(this)) {
    737                 mAnswerFragment = new AccessibleAnswerFragment();
    738             } else {
    739                 mAnswerFragment = new GlowPadAnswerFragment();
    740             }
    741             return mAnswerFragment;
    742         } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
    743             mConferenceManagerFragment = new ConferenceManagerFragment();
    744             return mConferenceManagerFragment;
    745         } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
    746             mCallCardFragment = new CallCardFragment();
    747             return mCallCardFragment;
    748         }
    749         throw new IllegalStateException("Unexpected fragment: " + tag);
    750     }
    751 
    752     private FragmentManager getFragmentManagerForTag(String tag) {
    753         if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
    754             return mChildFragmentManager;
    755         } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
    756             return mChildFragmentManager;
    757         } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
    758             return getFragmentManager();
    759         } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
    760             return getFragmentManager();
    761         }
    762         throw new IllegalStateException("Unexpected fragment: " + tag);
    763     }
    764 
    765     private int getScreenTypeForTag(String tag) {
    766         switch (tag) {
    767             case TAG_DIALPAD_FRAGMENT:
    768                 return ScreenEvent.INCALL_DIALPAD;
    769             case TAG_CALLCARD_FRAGMENT:
    770                 return ScreenEvent.INCALL;
    771             case TAG_CONFERENCE_FRAGMENT:
    772                 return ScreenEvent.CONFERENCE_MANAGEMENT;
    773             case TAG_ANSWER_FRAGMENT:
    774                 return ScreenEvent.INCOMING_CALL;
    775             default:
    776                 return ScreenEvent.UNKNOWN;
    777         }
    778     }
    779 
    780     private int getContainerIdForFragment(String tag) {
    781         if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
    782             return R.id.answer_and_dialpad_container;
    783         } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
    784             return R.id.answer_and_dialpad_container;
    785         } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
    786             return R.id.main;
    787         } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
    788             return R.id.main;
    789         }
    790         throw new IllegalStateException("Unexpected fragment: " + tag);
    791     }
    792 
    793     /**
    794      * @return {@code true} while the visibility of the dialpad has actually changed.
    795      */
    796     public boolean showDialpadFragment(boolean show, boolean animate) {
    797         // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
    798         if ((show && isDialpadVisible()) || (!show && !isDialpadVisible())) {
    799             return false;
    800         }
    801         // We don't do a FragmentTransaction on the hide case because it will be dealt with when
    802         // the listener is fired after an animation finishes.
    803         if (!animate) {
    804             showFragment(TAG_DIALPAD_FRAGMENT, show, true);
    805         } else {
    806             if (show) {
    807                 showFragment(TAG_DIALPAD_FRAGMENT, true, true);
    808                 mDialpadFragment.animateShowDialpad();
    809             }
    810             mDialpadFragment.getView().startAnimation(show ? mSlideIn : mSlideOut);
    811         }
    812         // Note:  onDialpadVisibilityChange is called here to ensure that the dialpad FAB
    813         // repositions itself.
    814         mCallCardFragment.onDialpadVisibilityChange(show);
    815 
    816         final ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
    817         if (sensor != null) {
    818             sensor.onDialpadVisible(show);
    819         }
    820         return true;
    821     }
    822 
    823     public boolean isDialpadVisible() {
    824         return mDialpadFragment != null && mDialpadFragment.isVisible();
    825     }
    826 
    827     public void showCallCardFragment(boolean show) {
    828         showFragment(TAG_CALLCARD_FRAGMENT, show, true);
    829     }
    830 
    831     /**
    832      * Hides or shows the conference manager fragment.
    833      *
    834      * @param show {@code true} if the conference manager should be shown, {@code false} if it
    835      * should be hidden.
    836      */
    837     public void showConferenceFragment(boolean show) {
    838         showFragment(TAG_CONFERENCE_FRAGMENT, show, true);
    839         mConferenceManagerFragment.onVisibilityChanged(show);
    840 
    841         // Need to hide the call card fragment to ensure that accessibility service does not try to
    842         // give focus to the call card when the conference manager is visible.
    843         mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE);
    844     }
    845 
    846     public void showAnswerFragment(boolean show) {
    847         showFragment(TAG_ANSWER_FRAGMENT, show, true);
    848     }
    849 
    850     public void showPostCharWaitDialog(String callId, String chars) {
    851         if (isVisible()) {
    852             final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
    853             fragment.show(getFragmentManager(), "postCharWait");
    854 
    855             mShowPostCharWaitDialogOnResume = false;
    856             mShowPostCharWaitDialogCallId = null;
    857             mShowPostCharWaitDialogChars = null;
    858         } else {
    859             mShowPostCharWaitDialogOnResume = true;
    860             mShowPostCharWaitDialogCallId = callId;
    861             mShowPostCharWaitDialogChars = chars;
    862         }
    863     }
    864 
    865     @Override
    866     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    867         if (mCallCardFragment != null) {
    868             mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
    869         }
    870         return super.dispatchPopulateAccessibilityEvent(event);
    871     }
    872 
    873     public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
    874         Log.d(this, "maybeShowErrorDialogOnDisconnect");
    875 
    876         if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription())
    877                 && (disconnectCause.getCode() == DisconnectCause.ERROR ||
    878                 disconnectCause.getCode() == DisconnectCause.RESTRICTED)) {
    879             showErrorDialog(disconnectCause.getDescription());
    880         }
    881     }
    882 
    883     public void dismissPendingDialogs() {
    884         if (mDialog != null) {
    885             mDialog.dismiss();
    886             mDialog = null;
    887         }
    888         if (mAnswerFragment != null) {
    889             mAnswerFragment.dismissPendingDialogs();
    890         }
    891     }
    892 
    893     /**
    894      * Utility function to bring up a generic "error" dialog.
    895      */
    896     private void showErrorDialog(CharSequence msg) {
    897         Log.i(this, "Show Dialog: " + msg);
    898 
    899         dismissPendingDialogs();
    900 
    901         mDialog = new AlertDialog.Builder(this)
    902                 .setMessage(msg)
    903                 .setPositiveButton(android.R.string.ok, new OnClickListener() {
    904                     @Override
    905                     public void onClick(DialogInterface dialog, int which) {
    906                         onDialogDismissed();
    907                     }
    908                 })
    909                 .setOnCancelListener(new OnCancelListener() {
    910                     @Override
    911                     public void onCancel(DialogInterface dialog) {
    912                         onDialogDismissed();
    913                     }
    914                 })
    915                 .create();
    916 
    917         mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    918         mDialog.show();
    919     }
    920 
    921     private void onDialogDismissed() {
    922         mDialog = null;
    923         CallList.getInstance().onErrorDialogDismissed();
    924         InCallPresenter.getInstance().onDismissDialog();
    925     }
    926 
    927     public void setExcludeFromRecents(boolean exclude) {
    928         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    929         List<ActivityManager.AppTask> tasks = am.getAppTasks();
    930         int taskId = getTaskId();
    931         for (int i = 0; i < tasks.size(); i++) {
    932             ActivityManager.AppTask task = tasks.get(i);
    933             if (task.getTaskInfo().id == taskId) {
    934                 try {
    935                     task.setExcludeFromRecents(exclude);
    936                 } catch (RuntimeException e) {
    937                     Log.e(TAG, "RuntimeException when excluding task from recents.", e);
    938                 }
    939             }
    940         }
    941     }
    942 
    943 
    944     public OnTouchListener getDispatchTouchEventListener() {
    945         return mDispatchTouchEventListener;
    946     }
    947 
    948     public void setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener) {
    949         this.mDispatchTouchEventListener = mDispatchTouchEventListener;
    950     }
    951 
    952     /**
    953      * Enables the OrientationEventListener if enable flag is true. Disables it if enable is
    954      * false
    955      * @param enable true or false.
    956      */
    957     public void enableInCallOrientationEventListener(boolean enable) {
    958         if (enable) {
    959             mInCallOrientationEventListener.enable(enable);
    960         } else {
    961             mInCallOrientationEventListener.disable();
    962         }
    963     }
    964 }
    965