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.AlertDialog;
     22 import android.app.FragmentManager;
     23 import android.app.FragmentTransaction;
     24 import android.content.Context;
     25 import android.content.DialogInterface;
     26 import android.content.DialogInterface.OnClickListener;
     27 import android.content.DialogInterface.OnCancelListener;
     28 import android.content.Intent;
     29 import android.content.res.Configuration;
     30 import android.content.res.Resources;
     31 import android.graphics.Point;
     32 import android.net.Uri;
     33 import android.os.Bundle;
     34 import android.telecom.DisconnectCause;
     35 import android.telecom.PhoneAccountHandle;
     36 import android.telecom.TelecomManager;
     37 import android.telephony.PhoneNumberUtils;
     38 import android.text.TextUtils;
     39 import android.view.MenuItem;
     40 import android.view.animation.Animation;
     41 import android.view.animation.AnimationUtils;
     42 import android.view.KeyEvent;
     43 import android.view.View;
     44 import android.view.Window;
     45 import android.view.WindowManager;
     46 import android.view.accessibility.AccessibilityEvent;
     47 
     48 import com.android.phone.common.animation.AnimUtils;
     49 import com.android.phone.common.animation.AnimationListenerAdapter;
     50 import com.android.contacts.common.interactions.TouchPointManager;
     51 import com.android.contacts.common.util.MaterialColorMapUtils;
     52 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
     53 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
     54 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
     55 import com.android.incallui.Call.State;
     56 
     57 import java.util.ArrayList;
     58 import java.util.List;
     59 import java.util.Locale;
     60 
     61 /**
     62  * Phone app "in call" screen.
     63  */
     64 public class InCallActivity extends Activity {
     65 
     66     public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
     67     public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
     68     public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
     69     public static final String SHOW_CIRCULAR_REVEAL_EXTRA = "InCallActivity.show_circular_reveal";
     70 
     71     private CallButtonFragment mCallButtonFragment;
     72     private CallCardFragment mCallCardFragment;
     73     private AnswerFragment mAnswerFragment;
     74     private DialpadFragment mDialpadFragment;
     75     private ConferenceManagerFragment mConferenceManagerFragment;
     76     private FragmentManager mChildFragmentManager;
     77 
     78     private boolean mIsForegroundActivity;
     79     private AlertDialog mDialog;
     80 
     81     /** Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume} */
     82     private boolean mShowDialpadRequested;
     83 
     84     /** Use to determine if the dialpad should be animated on show. */
     85     private boolean mAnimateDialpadOnShow;
     86 
     87     /** Use to determine the DTMF Text which should be pre-populated in the dialpad. */
     88     private String mDtmfText;
     89 
     90     /** Use to pass parameters for showing the PostCharDialog to {@link #onResume} */
     91     private boolean mShowPostCharWaitDialogOnResume;
     92     private String mShowPostCharWaitDialogCallId;
     93     private String mShowPostCharWaitDialogChars;
     94 
     95     private boolean mIsLandscape;
     96     private Animation mSlideIn;
     97     private Animation mSlideOut;
     98     private boolean mDismissKeyguard = false;
     99 
    100     AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
    101         @Override
    102         public void onAnimationEnd(Animation animation) {
    103             showDialpad(false);
    104         }
    105     };
    106 
    107     /**
    108      * Stores the current orientation of the activity.  Used to determine if a change in orientation
    109      * has occurred.
    110      */
    111     private int mCurrentOrientation;
    112 
    113     @Override
    114     protected void onCreate(Bundle icicle) {
    115         Log.d(this, "onCreate()...  this = " + this);
    116 
    117         super.onCreate(icicle);
    118 
    119         // set this flag so this activity will stay in front of the keyguard
    120         // Have the WindowManager filter out touch events that are "too fat".
    121         int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    122                 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
    123                 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
    124 
    125         getWindow().addFlags(flags);
    126 
    127         // Setup action bar for the conference call manager.
    128         requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
    129         ActionBar actionBar = getActionBar();
    130         if (actionBar != null) {
    131             actionBar.setDisplayHomeAsUpEnabled(true);
    132             actionBar.setDisplayShowTitleEnabled(true);
    133             actionBar.hide();
    134         }
    135 
    136         // TODO(klp): Do we need to add this back when prox sensor is not available?
    137         // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
    138 
    139         // Inflate everything in incall_screen.xml and add it to the screen.
    140         setContentView(R.layout.incall_screen);
    141 
    142         initializeInCall();
    143 
    144         internalResolveIntent(getIntent());
    145 
    146         mCurrentOrientation = getResources().getConfiguration().orientation;
    147         mIsLandscape = getResources().getConfiguration().orientation
    148                 == Configuration.ORIENTATION_LANDSCAPE;
    149 
    150         final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
    151                 View.LAYOUT_DIRECTION_RTL;
    152 
    153         if (mIsLandscape) {
    154             mSlideIn = AnimationUtils.loadAnimation(this,
    155                     isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
    156             mSlideOut = AnimationUtils.loadAnimation(this,
    157                     isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
    158         } else {
    159             mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
    160             mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
    161         }
    162 
    163         mSlideIn.setInterpolator(AnimUtils.EASE_IN);
    164         mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
    165 
    166         mSlideOut.setAnimationListener(mSlideOutListener);
    167 
    168         if (icicle != null) {
    169             // If the dialpad was shown before, set variables indicating it should be shown and
    170             // populated with the previous DTMF text.  The dialpad is actually shown and populated
    171             // in onResume() to ensure the hosting CallCardFragment has been inflated and is ready
    172             // to receive it.
    173             mShowDialpadRequested = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
    174             mAnimateDialpadOnShow = false;
    175             mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
    176         }
    177         Log.d(this, "onCreate(): exit");
    178     }
    179 
    180     @Override
    181     protected void onSaveInstanceState(Bundle out) {
    182         out.putBoolean(SHOW_DIALPAD_EXTRA, mCallButtonFragment.isDialpadVisible());
    183         if (mDialpadFragment != null) {
    184             out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText());
    185         }
    186         super.onSaveInstanceState(out);
    187     }
    188 
    189     @Override
    190     protected void onStart() {
    191         Log.d(this, "onStart()...");
    192         super.onStart();
    193 
    194         // setting activity should be last thing in setup process
    195         InCallPresenter.getInstance().setActivity(this);
    196     }
    197 
    198     @Override
    199     protected void onResume() {
    200         Log.i(this, "onResume()...");
    201         super.onResume();
    202 
    203         mIsForegroundActivity = true;
    204 
    205         InCallPresenter.getInstance().setThemeColors();
    206         InCallPresenter.getInstance().onUiShowing(true);
    207 
    208         if (mShowDialpadRequested) {
    209             mCallButtonFragment.displayDialpad(true /* show */,
    210                     mAnimateDialpadOnShow /* animate */);
    211             mShowDialpadRequested = false;
    212             mAnimateDialpadOnShow = false;
    213 
    214             if (mDialpadFragment != null) {
    215                 mDialpadFragment.setDtmfText(mDtmfText);
    216                 mDtmfText = null;
    217             }
    218         }
    219 
    220         if (mShowPostCharWaitDialogOnResume) {
    221             showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
    222         }
    223     }
    224 
    225     // onPause is guaranteed to be called when the InCallActivity goes
    226     // in the background.
    227     @Override
    228     protected void onPause() {
    229         Log.d(this, "onPause()...");
    230         super.onPause();
    231 
    232         mIsForegroundActivity = false;
    233 
    234         if (mDialpadFragment != null ) {
    235             mDialpadFragment.onDialerKeyUp(null);
    236         }
    237 
    238         InCallPresenter.getInstance().onUiShowing(false);
    239         if (isFinishing()) {
    240             InCallPresenter.getInstance().unsetActivity(this);
    241         }
    242     }
    243 
    244     @Override
    245     protected void onStop() {
    246         Log.d(this, "onStop()...");
    247         super.onStop();
    248     }
    249 
    250     @Override
    251     protected void onDestroy() {
    252         Log.d(this, "onDestroy()...  this = " + this);
    253         InCallPresenter.getInstance().unsetActivity(this);
    254         super.onDestroy();
    255     }
    256 
    257     /**
    258      * Returns true when theActivity is in foreground (between onResume and onPause).
    259      */
    260     /* package */ boolean isForegroundActivity() {
    261         return mIsForegroundActivity;
    262     }
    263 
    264     private boolean hasPendingErrorDialog() {
    265         return mDialog != null;
    266     }
    267 
    268     /**
    269      * Dismisses the in-call screen.
    270      *
    271      * We never *really* finish() the InCallActivity, since we don't want to get destroyed and then
    272      * have to be re-created from scratch for the next call.  Instead, we just move ourselves to the
    273      * back of the activity stack.
    274      *
    275      * This also means that we'll no longer be reachable via the BACK button (since moveTaskToBack()
    276      * puts us behind the Home app, but the home app doesn't allow the BACK key to move you any
    277      * farther down in the history stack.)
    278      *
    279      * (Since the Phone app itself is never killed, this basically means that we'll keep a single
    280      * InCallActivity instance around for the entire uptime of the device.  This noticeably improves
    281      * the UI responsiveness for incoming calls.)
    282      */
    283     @Override
    284     public void finish() {
    285         Log.i(this, "finish().  Dialog showing: " + (mDialog != null));
    286 
    287         // skip finish if we are still showing a dialog.
    288         if (!hasPendingErrorDialog() && !mAnswerFragment.hasPendingDialogs()) {
    289             super.finish();
    290         }
    291     }
    292 
    293     @Override
    294     protected void onNewIntent(Intent intent) {
    295         Log.d(this, "onNewIntent: intent = " + intent);
    296 
    297         // We're being re-launched with a new Intent.  Since it's possible for a
    298         // single InCallActivity instance to persist indefinitely (even if we
    299         // finish() ourselves), this sequence can potentially happen any time
    300         // the InCallActivity needs to be displayed.
    301 
    302         // Stash away the new intent so that we can get it in the future
    303         // by calling getIntent().  (Otherwise getIntent() will return the
    304         // original Intent from when we first got created!)
    305         setIntent(intent);
    306 
    307         // Activities are always paused before receiving a new intent, so
    308         // we can count on our onResume() method being called next.
    309 
    310         // Just like in onCreate(), handle the intent.
    311         internalResolveIntent(intent);
    312     }
    313 
    314     @Override
    315     public void onBackPressed() {
    316         Log.i(this, "onBackPressed");
    317 
    318         // BACK is also used to exit out of any "special modes" of the
    319         // in-call UI:
    320 
    321         if (!mConferenceManagerFragment.isVisible() && !mCallCardFragment.isVisible()) {
    322             return;
    323         }
    324 
    325         if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
    326             mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
    327             return;
    328         } else if (mConferenceManagerFragment.isVisible()) {
    329             showConferenceCallManager(false);
    330             return;
    331         }
    332 
    333         // Always disable the Back key while an incoming call is ringing
    334         final Call call = CallList.getInstance().getIncomingCall();
    335         if (call != null) {
    336             Log.d(this, "Consume Back press for an incoming call");
    337             return;
    338         }
    339 
    340         // Nothing special to do.  Fall back to the default behavior.
    341         super.onBackPressed();
    342     }
    343 
    344     @Override
    345     public boolean onOptionsItemSelected(MenuItem item) {
    346         final int itemId = item.getItemId();
    347         if (itemId == android.R.id.home) {
    348             onBackPressed();
    349             return true;
    350         }
    351         return super.onOptionsItemSelected(item);
    352     }
    353 
    354     @Override
    355     public boolean onKeyUp(int keyCode, KeyEvent event) {
    356         // push input to the dialer.
    357         if (mDialpadFragment != null && (mDialpadFragment.isVisible()) &&
    358                 (mDialpadFragment.onDialerKeyUp(event))){
    359             return true;
    360         } else if (keyCode == KeyEvent.KEYCODE_CALL) {
    361             // Always consume CALL to be sure the PhoneWindow won't do anything with it
    362             return true;
    363         }
    364         return super.onKeyUp(keyCode, event);
    365     }
    366 
    367     @Override
    368     public boolean onKeyDown(int keyCode, KeyEvent event) {
    369         switch (keyCode) {
    370             case KeyEvent.KEYCODE_CALL:
    371                 boolean handled = InCallPresenter.getInstance().handleCallKey();
    372                 if (!handled) {
    373                     Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown");
    374                 }
    375                 // Always consume CALL to be sure the PhoneWindow won't do anything with it
    376                 return true;
    377 
    378             // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
    379             // The standard system-wide handling of the ENDCALL key
    380             // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
    381             // already implements exactly what the UI spec wants,
    382             // namely (1) "hang up" if there's a current active call,
    383             // or (2) "don't answer" if there's a current ringing call.
    384 
    385             case KeyEvent.KEYCODE_CAMERA:
    386                 // Disable the CAMERA button while in-call since it's too
    387                 // easy to press accidentally.
    388                 return true;
    389 
    390             case KeyEvent.KEYCODE_VOLUME_UP:
    391             case KeyEvent.KEYCODE_VOLUME_DOWN:
    392             case KeyEvent.KEYCODE_VOLUME_MUTE:
    393                 // Ringer silencing handled by PhoneWindowManager.
    394                 break;
    395 
    396             case KeyEvent.KEYCODE_MUTE:
    397                 // toggle mute
    398                 TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute());
    399                 return true;
    400 
    401             // Various testing/debugging features, enabled ONLY when VERBOSE == true.
    402             case KeyEvent.KEYCODE_SLASH:
    403                 if (Log.VERBOSE) {
    404                     Log.v(this, "----------- InCallActivity View dump --------------");
    405                     // Dump starting from the top-level view of the entire activity:
    406                     Window w = this.getWindow();
    407                     View decorView = w.getDecorView();
    408                     Log.d(this, "View dump:" + decorView);
    409                     return true;
    410                 }
    411                 break;
    412             case KeyEvent.KEYCODE_EQUALS:
    413                 // TODO: Dump phone state?
    414                 break;
    415         }
    416 
    417         if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
    418             return true;
    419         }
    420 
    421         return super.onKeyDown(keyCode, event);
    422     }
    423 
    424     private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
    425         Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
    426 
    427         // As soon as the user starts typing valid dialable keys on the
    428         // keyboard (presumably to type DTMF tones) we start passing the
    429         // key events to the DTMFDialer's onDialerKeyDown.
    430         if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
    431             return mDialpadFragment.onDialerKeyDown(event);
    432 
    433             // TODO: If the dialpad isn't currently visible, maybe
    434             // consider automatically bringing it up right now?
    435             // (Just to make sure the user sees the digits widget...)
    436             // But this probably isn't too critical since it's awkward to
    437             // use the hard keyboard while in-call in the first place,
    438             // especially now that the in-call UI is portrait-only...
    439         }
    440 
    441         return false;
    442     }
    443 
    444     @Override
    445     public void onConfigurationChanged(Configuration config) {
    446         InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config);
    447         Log.d(this, "onConfigurationChanged "+config.orientation);
    448 
    449         // Check to see if the orientation changed to prevent triggering orientation change events
    450         // for other configuration changes.
    451         if (config.orientation != mCurrentOrientation) {
    452             mCurrentOrientation = config.orientation;
    453             InCallPresenter.getInstance().onDeviceRotationChange(
    454                     getWindowManager().getDefaultDisplay().getRotation());
    455             InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation);
    456         }
    457         super.onConfigurationChanged(config);
    458     }
    459 
    460     public CallButtonFragment getCallButtonFragment() {
    461         return mCallButtonFragment;
    462     }
    463 
    464     public CallCardFragment getCallCardFragment() {
    465         return mCallCardFragment;
    466     }
    467 
    468     private void internalResolveIntent(Intent intent) {
    469         final String action = intent.getAction();
    470 
    471         if (action.equals(intent.ACTION_MAIN)) {
    472             // This action is the normal way to bring up the in-call UI.
    473             //
    474             // But we do check here for one extra that can come along with the
    475             // ACTION_MAIN intent:
    476 
    477             if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
    478                 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
    479                 // dialpad should be initially visible.  If the extra isn't
    480                 // present at all, we just leave the dialpad in its previous state.
    481 
    482                 final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
    483                 Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
    484 
    485                 relaunchedFromDialer(showDialpad);
    486             }
    487 
    488             if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) {
    489                 intent.removeExtra(NEW_OUTGOING_CALL_EXTRA);
    490                 Call call = CallList.getInstance().getOutgoingCall();
    491                 if (call == null) {
    492                     call = CallList.getInstance().getPendingOutgoingCall();
    493                 }
    494 
    495                 Bundle extras = null;
    496                 if (call != null) {
    497                     extras = call.getTelecommCall().getDetails().getExtras();
    498                 }
    499                 if (extras == null) {
    500                     // Initialize the extras bundle to avoid NPE
    501                     extras = new Bundle();
    502                 }
    503 
    504                 Point touchPoint = null;
    505                 if (TouchPointManager.getInstance().hasValidPoint()) {
    506                     // Use the most immediate touch point in the InCallUi if available
    507                     touchPoint = TouchPointManager.getInstance().getPoint();
    508                 } else {
    509                     // Otherwise retrieve the touch point from the call intent
    510                     if (call != null) {
    511                         touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT);
    512                     }
    513                 }
    514 
    515                 // This is only true in the case where an outgoing call is initiated by tapping
    516                 // on the "Select account dialog", in which case we skip the initial animation. In
    517                 // most other cases the circular reveal is done by OutgoingCallAnimationActivity.
    518                 final boolean showCircularReveal =
    519                         intent.getBooleanExtra(SHOW_CIRCULAR_REVEAL_EXTRA, false);
    520                 mCallCardFragment.animateForNewOutgoingCall(touchPoint, showCircularReveal);
    521 
    522                 // InCallActivity is responsible for disconnecting a new outgoing call if there
    523                 // is no way of making it (i.e. no valid call capable accounts)
    524                 if (InCallPresenter.isCallWithNoValidAccounts(call)) {
    525                     TelecomAdapter.getInstance().disconnectCall(call.getId());
    526                 }
    527 
    528                 dismissKeyguard(true);
    529             }
    530 
    531             Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall();
    532             if (pendingAccountSelectionCall != null) {
    533                 mCallCardFragment.setVisible(false);
    534                 Bundle extras = pendingAccountSelectionCall
    535                         .getTelecommCall().getDetails().getExtras();
    536 
    537                 final List<PhoneAccountHandle> phoneAccountHandles;
    538                 if (extras != null) {
    539                     phoneAccountHandles = extras.getParcelableArrayList(
    540                             android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
    541                 } else {
    542                     phoneAccountHandles = new ArrayList<>();
    543                 }
    544 
    545                 SelectPhoneAccountListener listener = new SelectPhoneAccountListener() {
    546                     @Override
    547                     public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
    548                             boolean setDefault) {
    549                         InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle,
    550                                 setDefault);
    551                     }
    552                     @Override
    553                     public void onDialogDismissed() {
    554                         InCallPresenter.getInstance().cancelAccountSelection();
    555                     }
    556                 };
    557 
    558                 SelectPhoneAccountDialogFragment.showAccountDialog(getFragmentManager(),
    559                         R.string.select_phone_account_for_calls, true, phoneAccountHandles,
    560                         listener);
    561             } else {
    562                 mCallCardFragment.setVisible(true);
    563             }
    564 
    565             return;
    566         }
    567     }
    568 
    569     private void relaunchedFromDialer(boolean showDialpad) {
    570         mShowDialpadRequested = showDialpad;
    571         mAnimateDialpadOnShow = true;
    572 
    573         if (mShowDialpadRequested) {
    574             // If there's only one line in use, AND it's on hold, then we're sure the user
    575             // wants to use the dialpad toward the exact line, so un-hold the holding line.
    576             final Call call = CallList.getInstance().getActiveOrBackgroundCall();
    577             if (call != null && call.getState() == State.ONHOLD) {
    578                 TelecomAdapter.getInstance().unholdCall(call.getId());
    579             }
    580         }
    581     }
    582 
    583     private void initializeInCall() {
    584         if (mCallCardFragment == null) {
    585             mCallCardFragment = (CallCardFragment) getFragmentManager()
    586                     .findFragmentById(R.id.callCardFragment);
    587         }
    588 
    589         mChildFragmentManager = mCallCardFragment.getChildFragmentManager();
    590 
    591         if (mCallButtonFragment == null) {
    592             mCallButtonFragment = (CallButtonFragment) mChildFragmentManager
    593                     .findFragmentById(R.id.callButtonFragment);
    594             mCallButtonFragment.getView().setVisibility(View.INVISIBLE);
    595         }
    596 
    597         if (mAnswerFragment == null) {
    598             mAnswerFragment = (AnswerFragment) mChildFragmentManager
    599                     .findFragmentById(R.id.answerFragment);
    600         }
    601 
    602         if (mConferenceManagerFragment == null) {
    603             mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager()
    604                     .findFragmentById(R.id.conferenceManagerFragment);
    605             mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE);
    606         }
    607     }
    608 
    609     /**
    610      * Simulates a user click to hide the dialpad. This will update the UI to show the call card,
    611      * update the checked state of the dialpad button, and update the proximity sensor state.
    612      */
    613     public void hideDialpadForDisconnect() {
    614         mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
    615     }
    616 
    617     public void dismissKeyguard(boolean dismiss) {
    618         if (mDismissKeyguard == dismiss) {
    619             return;
    620         }
    621         mDismissKeyguard = dismiss;
    622         if (dismiss) {
    623             getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    624         } else {
    625             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    626         }
    627     }
    628 
    629     private void showDialpad(boolean showDialpad) {
    630         // If the dialpad is being shown and it has not already been loaded, replace the dialpad
    631         // placeholder with the actual fragment before continuing.
    632         if (mDialpadFragment == null && showDialpad) {
    633             final FragmentTransaction loadTransaction = mChildFragmentManager.beginTransaction();
    634             View fragmentContainer = findViewById(R.id.dialpadFragmentContainer);
    635             mDialpadFragment = new DialpadFragment();
    636             loadTransaction.replace(fragmentContainer.getId(), mDialpadFragment,
    637                     DialpadFragment.class.getName());
    638             loadTransaction.commitAllowingStateLoss();
    639             mChildFragmentManager.executePendingTransactions();
    640         }
    641 
    642         final FragmentTransaction ft = mChildFragmentManager.beginTransaction();
    643         if (showDialpad) {
    644             ft.show(mDialpadFragment);
    645         } else {
    646             ft.hide(mDialpadFragment);
    647         }
    648         ft.commitAllowingStateLoss();
    649     }
    650 
    651     public void displayDialpad(boolean showDialpad, boolean animate) {
    652         // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
    653         if ((showDialpad && isDialpadVisible()) || (!showDialpad && !isDialpadVisible())) {
    654             return;
    655         }
    656         // We don't do a FragmentTransaction on the hide case because it will be dealt with when
    657         // the listener is fired after an animation finishes.
    658         if (!animate) {
    659             showDialpad(showDialpad);
    660         } else {
    661             if (showDialpad) {
    662                 showDialpad(true);
    663                 mDialpadFragment.animateShowDialpad();
    664             }
    665             mCallCardFragment.onDialpadVisiblityChange(showDialpad);
    666             mDialpadFragment.getView().startAnimation(showDialpad ? mSlideIn : mSlideOut);
    667         }
    668 
    669         InCallPresenter.getInstance().getProximitySensor().onDialpadVisible(showDialpad);
    670     }
    671 
    672     public boolean isDialpadVisible() {
    673         return mDialpadFragment != null && mDialpadFragment.isVisible();
    674     }
    675 
    676     /**
    677      * Hides or shows the conference manager fragment.
    678      *
    679      * @param show {@code true} if the conference manager should be shown, {@code false} if it
    680      *                         should be hidden.
    681      */
    682     public void showConferenceCallManager(boolean show) {
    683         mConferenceManagerFragment.setVisible(show);
    684 
    685         // Need to hide the call card fragment to ensure that accessibility service does not try to
    686         // give focus to the call card when the conference manager is visible.
    687         mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE);
    688     }
    689 
    690     public void showPostCharWaitDialog(String callId, String chars) {
    691         if (isForegroundActivity()) {
    692             final PostCharDialogFragment fragment = new PostCharDialogFragment(callId,  chars);
    693             fragment.show(getFragmentManager(), "postCharWait");
    694 
    695             mShowPostCharWaitDialogOnResume = false;
    696             mShowPostCharWaitDialogCallId = null;
    697             mShowPostCharWaitDialogChars = null;
    698         } else {
    699             mShowPostCharWaitDialogOnResume = true;
    700             mShowPostCharWaitDialogCallId = callId;
    701             mShowPostCharWaitDialogChars = chars;
    702         }
    703     }
    704 
    705     @Override
    706     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    707         if (mCallCardFragment != null) {
    708             mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
    709         }
    710         return super.dispatchPopulateAccessibilityEvent(event);
    711     }
    712 
    713     public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
    714         Log.d(this, "maybeShowErrorDialogOnDisconnect");
    715 
    716         if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription())
    717                 && (disconnectCause.getCode() == DisconnectCause.ERROR ||
    718                         disconnectCause.getCode() == DisconnectCause.RESTRICTED)) {
    719             showErrorDialog(disconnectCause.getDescription());
    720         }
    721     }
    722 
    723     public void dismissPendingDialogs() {
    724         if (mDialog != null) {
    725             mDialog.dismiss();
    726             mDialog = null;
    727         }
    728         mAnswerFragment.dismissPendingDialogues();
    729     }
    730 
    731     /**
    732      * Utility function to bring up a generic "error" dialog.
    733      */
    734     private void showErrorDialog(CharSequence msg) {
    735         Log.i(this, "Show Dialog: " + msg);
    736 
    737         dismissPendingDialogs();
    738 
    739         mDialog = new AlertDialog.Builder(this)
    740                 .setMessage(msg)
    741                 .setPositiveButton(android.R.string.ok, new OnClickListener() {
    742                     @Override
    743                     public void onClick(DialogInterface dialog, int which) {
    744                         onDialogDismissed();
    745                     }})
    746                 .setOnCancelListener(new OnCancelListener() {
    747                     @Override
    748                     public void onCancel(DialogInterface dialog) {
    749                         onDialogDismissed();
    750                     }})
    751                 .create();
    752 
    753         mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
    754         mDialog.show();
    755     }
    756 
    757     private void onDialogDismissed() {
    758         mDialog = null;
    759         InCallPresenter.getInstance().onDismissDialog();
    760     }
    761 }
    762