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