InCallActivity.java revision b16bc3bb33bb17e0b05e82f085ba4df405d5e201
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
17package com.android.incallui;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.FragmentManager;
23import android.app.FragmentTransaction;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.DialogInterface.OnClickListener;
27import android.content.DialogInterface.OnCancelListener;
28import android.content.Intent;
29import android.content.res.Configuration;
30import android.content.res.Resources;
31import android.graphics.Point;
32import android.net.Uri;
33import android.os.Bundle;
34import android.telecom.DisconnectCause;
35import android.telecom.PhoneAccountHandle;
36import android.telecom.TelecomManager;
37import android.telephony.PhoneNumberUtils;
38import android.text.TextUtils;
39import android.view.MenuItem;
40import android.view.animation.Animation;
41import android.view.animation.AnimationUtils;
42import android.view.KeyEvent;
43import android.view.View;
44import android.view.Window;
45import android.view.WindowManager;
46import android.view.accessibility.AccessibilityEvent;
47
48import com.android.phone.common.animation.AnimUtils;
49import com.android.phone.common.animation.AnimationListenerAdapter;
50import com.android.contacts.common.interactions.TouchPointManager;
51import com.android.contacts.common.util.MaterialColorMapUtils;
52import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
53import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
54import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
55import com.android.incallui.Call.State;
56
57import java.util.ArrayList;
58import java.util.List;
59import java.util.Locale;
60
61/**
62 * Phone app "in call" screen.
63 */
64public 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