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