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