InCallActivity.java revision a62dc2266449adc677ddc7c8141e615f95e3652c
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.ActivityManager;
22import android.app.AlertDialog;
23import android.app.DialogFragment;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.DialogInterface.OnCancelListener;
30import android.content.DialogInterface.OnClickListener;
31import android.content.Intent;
32import android.content.res.Configuration;
33import android.graphics.Point;
34import android.hardware.SensorManager;
35import android.os.Bundle;
36import android.os.Trace;
37import android.telecom.DisconnectCause;
38import android.telecom.PhoneAccountHandle;
39import android.text.TextUtils;
40import android.view.KeyEvent;
41import android.view.MenuItem;
42import android.view.MotionEvent;
43import android.view.OrientationEventListener;
44import android.view.Surface;
45import android.view.View;
46import android.view.View.OnTouchListener;
47import android.view.Window;
48import android.view.WindowManager;
49import android.view.accessibility.AccessibilityEvent;
50import android.view.animation.Animation;
51import android.view.animation.AnimationUtils;
52
53import com.android.contacts.common.activity.TransactionSafeActivity;
54import com.android.contacts.common.interactions.TouchPointManager;
55import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
56import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
57import com.android.dialer.logging.Logger;
58import com.android.dialer.logging.ScreenEvent;
59import com.android.incallui.Call.State;
60import com.android.incallui.util.AccessibilityUtil;
61import com.android.phone.common.animation.AnimUtils;
62import com.android.phone.common.animation.AnimationListenerAdapter;
63
64import java.util.ArrayList;
65import java.util.List;
66import java.util.Locale;
67
68/**
69 * Main activity that the user interacts with while in a live call.
70 */
71public class InCallActivity extends TransactionSafeActivity implements FragmentDisplayManager {
72
73    public static final String TAG = InCallActivity.class.getSimpleName();
74
75    public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
76    public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
77    public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
78
79    private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
80    private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment";
81    private static final String TAG_CALLCARD_FRAGMENT = "tag_callcard_fragment";
82    private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment";
83    private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
84
85    private CallButtonFragment mCallButtonFragment;
86    private CallCardFragment mCallCardFragment;
87    private AnswerFragment mAnswerFragment;
88    private DialpadFragment mDialpadFragment;
89    private ConferenceManagerFragment mConferenceManagerFragment;
90    private FragmentManager mChildFragmentManager;
91
92    private AlertDialog mDialog;
93    private InCallOrientationEventListener mInCallOrientationEventListener;
94
95    /**
96     * Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume}
97     */
98    private boolean mShowDialpadRequested;
99
100    /**
101     * Use to determine if the dialpad should be animated on show.
102     */
103    private boolean mAnimateDialpadOnShow;
104
105    /**
106     * Use to determine the DTMF Text which should be pre-populated in the dialpad.
107     */
108    private String mDtmfText;
109
110    /**
111     * Use to pass parameters for showing the PostCharDialog to {@link #onResume}
112     */
113    private boolean mShowPostCharWaitDialogOnResume;
114    private String mShowPostCharWaitDialogCallId;
115    private String mShowPostCharWaitDialogChars;
116
117    private boolean mIsLandscape;
118    private Animation mSlideIn;
119    private Animation mSlideOut;
120    private boolean mDismissKeyguard = false;
121
122    AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
123        @Override
124        public void onAnimationEnd(Animation animation) {
125            showFragment(TAG_DIALPAD_FRAGMENT, false, true);
126        }
127    };
128
129    private OnTouchListener mDispatchTouchEventListener;
130
131    private SelectPhoneAccountListener mSelectAcctListener = new SelectPhoneAccountListener() {
132        @Override
133        public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle,
134                boolean setDefault) {
135            InCallPresenter.getInstance().handleAccountSelection(selectedAccountHandle,
136                    setDefault);
137        }
138
139        @Override
140        public void onDialogDismissed() {
141            InCallPresenter.getInstance().cancelAccountSelection();
142        }
143    };
144
145    @Override
146    protected void onCreate(Bundle icicle) {
147        Log.d(this, "onCreate()...  this = " + this);
148
149        super.onCreate(icicle);
150
151        // set this flag so this activity will stay in front of the keyguard
152        // Have the WindowManager filter out touch events that are "too fat".
153        int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
154                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
155                | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
156
157        getWindow().addFlags(flags);
158
159        // Setup action bar for the conference call manager.
160        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
161        ActionBar actionBar = getActionBar();
162        if (actionBar != null) {
163            actionBar.setDisplayHomeAsUpEnabled(true);
164            actionBar.setDisplayShowTitleEnabled(true);
165            actionBar.hide();
166        }
167
168        // TODO(klp): Do we need to add this back when prox sensor is not available?
169        // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
170
171        setContentView(R.layout.incall_screen);
172
173        internalResolveIntent(getIntent());
174
175        mIsLandscape = getResources().getConfiguration().orientation ==
176                Configuration.ORIENTATION_LANDSCAPE;
177
178        final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
179                View.LAYOUT_DIRECTION_RTL;
180
181        if (mIsLandscape) {
182            mSlideIn = AnimationUtils.loadAnimation(this,
183                    isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
184            mSlideOut = AnimationUtils.loadAnimation(this,
185                    isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
186        } else {
187            mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
188            mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
189        }
190
191        mSlideIn.setInterpolator(AnimUtils.EASE_IN);
192        mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
193
194        mSlideOut.setAnimationListener(mSlideOutListener);
195
196        if (icicle != null) {
197            // If the dialpad was shown before, set variables indicating it should be shown and
198            // populated with the previous DTMF text.  The dialpad is actually shown and populated
199            // in onResume() to ensure the hosting CallCardFragment has been inflated and is ready
200            // to receive it.
201            mShowDialpadRequested = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
202            mAnimateDialpadOnShow = false;
203            mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
204
205            SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
206                    getFragmentManager().findFragmentByTag(TAG_SELECT_ACCT_FRAGMENT);
207            if (dialogFragment != null) {
208                dialogFragment.setListener(mSelectAcctListener);
209            }
210        }
211
212        mInCallOrientationEventListener = new InCallOrientationEventListener(this);
213
214        Log.d(this, "onCreate(): exit");
215    }
216
217    @Override
218    protected void onSaveInstanceState(Bundle out) {
219        // TODO: The dialpad fragment should handle this as part of its own state
220        out.putBoolean(SHOW_DIALPAD_EXTRA,
221                mCallButtonFragment != null && mCallButtonFragment.isDialpadVisible());
222        if (mDialpadFragment != null) {
223            out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText());
224        }
225        super.onSaveInstanceState(out);
226    }
227
228    @Override
229    protected void onStart() {
230        Log.d(this, "onStart()...");
231        super.onStart();
232
233        // setting activity should be last thing in setup process
234        InCallPresenter.getInstance().setActivity(this);
235        enableInCallOrientationEventListener(getRequestedOrientation() ==
236               InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
237
238        InCallPresenter.getInstance().onActivityStarted();
239    }
240
241    @Override
242    protected void onResume() {
243        Log.i(this, "onResume()...");
244        super.onResume();
245
246        InCallPresenter.getInstance().setThemeColors();
247        InCallPresenter.getInstance().onUiShowing(true);
248
249        if (mShowDialpadRequested) {
250            mCallButtonFragment.displayDialpad(true /* show */,
251                    mAnimateDialpadOnShow /* animate */);
252            mShowDialpadRequested = false;
253            mAnimateDialpadOnShow = false;
254
255            if (mDialpadFragment != null) {
256                mDialpadFragment.setDtmfText(mDtmfText);
257                mDtmfText = null;
258            }
259        }
260
261        if (mShowPostCharWaitDialogOnResume) {
262            showPostCharWaitDialog(mShowPostCharWaitDialogCallId, mShowPostCharWaitDialogChars);
263        }
264    }
265
266    // onPause is guaranteed to be called when the InCallActivity goes
267    // in the background.
268    @Override
269    protected void onPause() {
270        Log.d(this, "onPause()...");
271        if (mDialpadFragment != null) {
272            mDialpadFragment.onDialerKeyUp(null);
273        }
274
275        InCallPresenter.getInstance().onUiShowing(false);
276        if (isFinishing()) {
277            InCallPresenter.getInstance().unsetActivity(this);
278        }
279        super.onPause();
280    }
281
282    @Override
283    protected void onStop() {
284        Log.d(this, "onStop()...");
285        enableInCallOrientationEventListener(false);
286        InCallPresenter.getInstance().updateIsChangingConfigurations();
287        InCallPresenter.getInstance().onActivityStopped();
288        super.onStop();
289    }
290
291    @Override
292    protected void onDestroy() {
293        Log.d(this, "onDestroy()...  this = " + this);
294        InCallPresenter.getInstance().unsetActivity(this);
295        InCallPresenter.getInstance().updateIsChangingConfigurations();
296        super.onDestroy();
297    }
298
299    /**
300     * When fragments have a parent fragment, onAttachFragment is not called on the parent
301     * activity. To fix this, register our own callback instead that is always called for
302     * all fragments.
303     *
304     * @see {@link BaseFragment#onAttach(Activity)}
305     */
306    @Override
307    public void onFragmentAttached(Fragment fragment) {
308        if (fragment instanceof DialpadFragment) {
309            mDialpadFragment = (DialpadFragment) fragment;
310        } else if (fragment instanceof AnswerFragment) {
311            mAnswerFragment = (AnswerFragment) fragment;
312        } else if (fragment instanceof CallCardFragment) {
313            mCallCardFragment = (CallCardFragment) fragment;
314            mChildFragmentManager = mCallCardFragment.getChildFragmentManager();
315        } else if (fragment instanceof ConferenceManagerFragment) {
316            mConferenceManagerFragment = (ConferenceManagerFragment) fragment;
317        } else if (fragment instanceof CallButtonFragment) {
318            mCallButtonFragment = (CallButtonFragment) fragment;
319        }
320    }
321
322    /**
323     * Returns true when the Activity is currently visible.
324     */
325    /* package */ boolean isVisible() {
326        return isSafeToCommitTransactions();
327    }
328
329    private boolean hasPendingDialogs() {
330        return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs());
331    }
332
333    @Override
334    public void finish() {
335        Log.i(this, "finish().  Dialog showing: " + (mDialog != null));
336
337        // skip finish if we are still showing a dialog.
338        if (!hasPendingDialogs()) {
339            super.finish();
340        }
341    }
342
343    @Override
344    protected void onNewIntent(Intent intent) {
345        Log.d(this, "onNewIntent: intent = " + intent);
346
347        // We're being re-launched with a new Intent.  Since it's possible for a
348        // single InCallActivity instance to persist indefinitely (even if we
349        // finish() ourselves), this sequence can potentially happen any time
350        // the InCallActivity needs to be displayed.
351
352        // Stash away the new intent so that we can get it in the future
353        // by calling getIntent().  (Otherwise getIntent() will return the
354        // original Intent from when we first got created!)
355        setIntent(intent);
356
357        // Activities are always paused before receiving a new intent, so
358        // we can count on our onResume() method being called next.
359
360        // Just like in onCreate(), handle the intent.
361        internalResolveIntent(intent);
362    }
363
364    @Override
365    public void onBackPressed() {
366        Log.i(this, "onBackPressed");
367
368        // BACK is also used to exit out of any "special modes" of the
369        // in-call UI:
370        if (!isVisible()) {
371            return;
372        }
373
374        if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible())
375                && (mCallCardFragment == null || !mCallCardFragment.isVisible())) {
376            return;
377        }
378
379        if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
380            mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
381            return;
382        } else if (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible()) {
383            showConferenceFragment(false);
384            return;
385        }
386
387        // Always disable the Back key while an incoming call is ringing
388        final Call call = CallList.getInstance().getIncomingCall();
389        if (call != null) {
390            Log.i(this, "Consume Back press for an incoming call");
391            return;
392        }
393
394        // Nothing special to do.  Fall back to the default behavior.
395        super.onBackPressed();
396    }
397
398    @Override
399    public boolean onOptionsItemSelected(MenuItem item) {
400        final int itemId = item.getItemId();
401        if (itemId == android.R.id.home) {
402            onBackPressed();
403            return true;
404        }
405        return super.onOptionsItemSelected(item);
406    }
407
408    @Override
409    public boolean onKeyUp(int keyCode, KeyEvent event) {
410        // push input to the dialer.
411        if (mDialpadFragment != null && (mDialpadFragment.isVisible()) &&
412                (mDialpadFragment.onDialerKeyUp(event))) {
413            return true;
414        } else if (keyCode == KeyEvent.KEYCODE_CALL) {
415            // Always consume CALL to be sure the PhoneWindow won't do anything with it
416            return true;
417        }
418        return super.onKeyUp(keyCode, event);
419    }
420
421    @Override
422    public boolean dispatchTouchEvent(MotionEvent ev) {
423        if (mDispatchTouchEventListener != null) {
424            boolean handled = mDispatchTouchEventListener.onTouch(null, ev);
425            if (handled) {
426                return true;
427            }
428        }
429        return super.dispatchTouchEvent(ev);
430    }
431
432    @Override
433    public boolean onKeyDown(int keyCode, KeyEvent event) {
434        switch (keyCode) {
435            case KeyEvent.KEYCODE_CALL:
436                boolean handled = InCallPresenter.getInstance().handleCallKey();
437                if (!handled) {
438                    Log.w(this, "InCallActivity should always handle KEYCODE_CALL in onKeyDown");
439                }
440                // Always consume CALL to be sure the PhoneWindow won't do anything with it
441                return true;
442
443            // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
444            // The standard system-wide handling of the ENDCALL key
445            // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
446            // already implements exactly what the UI spec wants,
447            // namely (1) "hang up" if there's a current active call,
448            // or (2) "don't answer" if there's a current ringing call.
449
450            case KeyEvent.KEYCODE_CAMERA:
451                // Disable the CAMERA button while in-call since it's too
452                // easy to press accidentally.
453                return true;
454
455            case KeyEvent.KEYCODE_VOLUME_UP:
456            case KeyEvent.KEYCODE_VOLUME_DOWN:
457            case KeyEvent.KEYCODE_VOLUME_MUTE:
458                // Ringer silencing handled by PhoneWindowManager.
459                break;
460
461            case KeyEvent.KEYCODE_MUTE:
462                // toggle mute
463                TelecomAdapter.getInstance().mute(!AudioModeProvider.getInstance().getMute());
464                return true;
465
466            // Various testing/debugging features, enabled ONLY when VERBOSE == true.
467            case KeyEvent.KEYCODE_SLASH:
468                if (Log.VERBOSE) {
469                    Log.v(this, "----------- InCallActivity View dump --------------");
470                    // Dump starting from the top-level view of the entire activity:
471                    Window w = this.getWindow();
472                    View decorView = w.getDecorView();
473                    Log.d(this, "View dump:" + decorView);
474                    return true;
475                }
476                break;
477            case KeyEvent.KEYCODE_EQUALS:
478                // TODO: Dump phone state?
479                break;
480        }
481
482        if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
483            return true;
484        }
485        return super.onKeyDown(keyCode, event);
486    }
487
488    private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
489        Log.v(this, "handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
490
491        // As soon as the user starts typing valid dialable keys on the
492        // keyboard (presumably to type DTMF tones) we start passing the
493        // key events to the DTMFDialer's onDialerKeyDown.
494        if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
495            return mDialpadFragment.onDialerKeyDown(event);
496        }
497
498        return false;
499    }
500
501    public CallButtonFragment getCallButtonFragment() {
502        return mCallButtonFragment;
503    }
504
505    public CallCardFragment getCallCardFragment() {
506        return mCallCardFragment;
507    }
508
509    public AnswerFragment getAnswerFragment() {
510        return mAnswerFragment;
511    }
512
513    private void internalResolveIntent(Intent intent) {
514        final String action = intent.getAction();
515        if (action.equals(Intent.ACTION_MAIN)) {
516            // This action is the normal way to bring up the in-call UI.
517            //
518            // But we do check here for one extra that can come along with the
519            // ACTION_MAIN intent:
520
521            if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
522                // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
523                // dialpad should be initially visible.  If the extra isn't
524                // present at all, we just leave the dialpad in its previous state.
525
526                final boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
527                Log.d(this, "- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
528
529                relaunchedFromDialer(showDialpad);
530            }
531
532            boolean newOutgoingCall = false;
533            if (intent.getBooleanExtra(NEW_OUTGOING_CALL_EXTRA, false)) {
534                intent.removeExtra(NEW_OUTGOING_CALL_EXTRA);
535                Call call = CallList.getInstance().getOutgoingCall();
536                if (call == null) {
537                    call = CallList.getInstance().getPendingOutgoingCall();
538                }
539
540                Bundle extras = null;
541                if (call != null) {
542                    extras = call.getTelecomCall().getDetails().getIntentExtras();
543                }
544                if (extras == null) {
545                    // Initialize the extras bundle to avoid NPE
546                    extras = new Bundle();
547                }
548
549                Point touchPoint = null;
550                if (TouchPointManager.getInstance().hasValidPoint()) {
551                    // Use the most immediate touch point in the InCallUi if available
552                    touchPoint = TouchPointManager.getInstance().getPoint();
553                } else {
554                    // Otherwise retrieve the touch point from the call intent
555                    if (call != null) {
556                        touchPoint = (Point) extras.getParcelable(TouchPointManager.TOUCH_POINT);
557                    }
558                }
559
560                // Start animation for new outgoing call
561                CircularRevealFragment.startCircularReveal(getFragmentManager(), touchPoint,
562                        InCallPresenter.getInstance());
563
564                // InCallActivity is responsible for disconnecting a new outgoing call if there
565                // is no way of making it (i.e. no valid call capable accounts)
566                if (InCallPresenter.isCallWithNoValidAccounts(call)) {
567                    TelecomAdapter.getInstance().disconnectCall(call.getId());
568                }
569
570                dismissKeyguard(true);
571                newOutgoingCall = true;
572            }
573
574            Call pendingAccountSelectionCall = CallList.getInstance().getWaitingForAccountCall();
575            if (pendingAccountSelectionCall != null) {
576                showCallCardFragment(false);
577                Bundle extras = pendingAccountSelectionCall
578                        .getTelecomCall().getDetails().getIntentExtras();
579
580                final List<PhoneAccountHandle> phoneAccountHandles;
581                if (extras != null) {
582                    phoneAccountHandles = extras.getParcelableArrayList(
583                            android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
584                } else {
585                    phoneAccountHandles = new ArrayList<>();
586                }
587
588                DialogFragment dialogFragment = SelectPhoneAccountDialogFragment.newInstance(
589                        R.string.select_phone_account_for_calls, true, phoneAccountHandles,
590                        mSelectAcctListener);
591                dialogFragment.show(getFragmentManager(), TAG_SELECT_ACCT_FRAGMENT);
592            } else if (!newOutgoingCall) {
593                showCallCardFragment(true);
594            }
595
596            return;
597        }
598    }
599
600    private void relaunchedFromDialer(boolean showDialpad) {
601        mShowDialpadRequested = showDialpad;
602        mAnimateDialpadOnShow = true;
603
604        if (mShowDialpadRequested) {
605            // If there's only one line in use, AND it's on hold, then we're sure the user
606            // wants to use the dialpad toward the exact line, so un-hold the holding line.
607            final Call call = CallList.getInstance().getActiveOrBackgroundCall();
608            if (call != null && call.getState() == State.ONHOLD) {
609                TelecomAdapter.getInstance().unholdCall(call.getId());
610            }
611        }
612    }
613
614    public void dismissKeyguard(boolean dismiss) {
615        if (mDismissKeyguard == dismiss) {
616            return;
617        }
618        mDismissKeyguard = dismiss;
619        if (dismiss) {
620            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
621        } else {
622            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
623        }
624    }
625
626    private void showFragment(String tag, boolean show, boolean executeImmediately) {
627        Trace.beginSection("showFragment - " + tag);
628        final FragmentManager fm = getFragmentManagerForTag(tag);
629
630        if (fm == null) {
631            Log.w(TAG, "Fragment manager is null for : " + tag);
632            return;
633        }
634
635        Fragment fragment = fm.findFragmentByTag(tag);
636        if (!show && fragment == null) {
637            // Nothing to show, so bail early.
638            return;
639        }
640
641        final FragmentTransaction transaction = fm.beginTransaction();
642        if (show) {
643            if (fragment == null) {
644                fragment = createNewFragmentForTag(tag);
645                transaction.add(getContainerIdForFragment(tag), fragment, tag);
646            } else {
647                transaction.show(fragment);
648            }
649            Logger.logScreenView(getScreenTypeForTag(tag), this);
650        } else {
651            transaction.hide(fragment);
652        }
653
654        transaction.commitAllowingStateLoss();
655        if (executeImmediately) {
656            fm.executePendingTransactions();
657        }
658        Trace.endSection();
659    }
660
661    private Fragment createNewFragmentForTag(String tag) {
662        if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
663            mDialpadFragment = new DialpadFragment();
664            return mDialpadFragment;
665        } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
666            if (AccessibilityUtil.isTalkBackEnabled(this)) {
667                mAnswerFragment = new AccessibleAnswerFragment();
668            } else {
669                mAnswerFragment = new GlowPadAnswerFragment();
670            }
671            return mAnswerFragment;
672        } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
673            mConferenceManagerFragment = new ConferenceManagerFragment();
674            return mConferenceManagerFragment;
675        } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
676            mCallCardFragment = new CallCardFragment();
677            return mCallCardFragment;
678        }
679        throw new IllegalStateException("Unexpected fragment: " + tag);
680    }
681
682    private FragmentManager getFragmentManagerForTag(String tag) {
683        if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
684            return mChildFragmentManager;
685        } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
686            return mChildFragmentManager;
687        } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
688            return getFragmentManager();
689        } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
690            return getFragmentManager();
691        }
692        throw new IllegalStateException("Unexpected fragment: " + tag);
693    }
694
695    private int getScreenTypeForTag(String tag) {
696        switch (tag) {
697            case TAG_DIALPAD_FRAGMENT:
698                return ScreenEvent.INCALL_DIALPAD;
699            case TAG_CALLCARD_FRAGMENT:
700                return ScreenEvent.INCALL;
701            case TAG_CONFERENCE_FRAGMENT:
702                return ScreenEvent.CONFERENCE_MANAGEMENT;
703            case TAG_ANSWER_FRAGMENT:
704                return ScreenEvent.INCOMING_CALL;
705            default:
706                return ScreenEvent.UNKNOWN;
707        }
708    }
709
710    private int getContainerIdForFragment(String tag) {
711        if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
712            return R.id.answer_and_dialpad_container;
713        } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
714            return R.id.answer_and_dialpad_container;
715        } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
716            return R.id.main;
717        } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
718            return R.id.main;
719        }
720        throw new IllegalStateException("Unexpected fragment: " + tag);
721    }
722
723    /**
724     * @return {@code true} while the visibility of the dialpad has actually changed.
725     */
726    public boolean showDialpadFragment(boolean show, boolean animate) {
727        // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
728        if ((show && isDialpadVisible()) || (!show && !isDialpadVisible())) {
729            return false;
730        }
731        // We don't do a FragmentTransaction on the hide case because it will be dealt with when
732        // the listener is fired after an animation finishes.
733        if (!animate) {
734            showFragment(TAG_DIALPAD_FRAGMENT, show, true);
735        } else {
736            if (show) {
737                showFragment(TAG_DIALPAD_FRAGMENT, true, true);
738                mDialpadFragment.animateShowDialpad();
739            }
740            mCallCardFragment.onDialpadVisibilityChange(show);
741            mDialpadFragment.getView().startAnimation(show ? mSlideIn : mSlideOut);
742        }
743
744        final ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
745        if (sensor != null) {
746            sensor.onDialpadVisible(show);
747        }
748        return true;
749    }
750
751    public boolean isDialpadVisible() {
752        return mDialpadFragment != null && mDialpadFragment.isVisible();
753    }
754
755    public void showCallCardFragment(boolean show) {
756        showFragment(TAG_CALLCARD_FRAGMENT, show, true);
757    }
758
759    /**
760     * Hides or shows the conference manager fragment.
761     *
762     * @param show {@code true} if the conference manager should be shown, {@code false} if it
763     * should be hidden.
764     */
765    public void showConferenceFragment(boolean show) {
766        showFragment(TAG_CONFERENCE_FRAGMENT, show, true);
767        mConferenceManagerFragment.onVisibilityChanged(show);
768
769        // Need to hide the call card fragment to ensure that accessibility service does not try to
770        // give focus to the call card when the conference manager is visible.
771        mCallCardFragment.getView().setVisibility(show ? View.GONE : View.VISIBLE);
772    }
773
774    public void showAnswerFragment(boolean show) {
775        showFragment(TAG_ANSWER_FRAGMENT, show, true);
776    }
777
778    public void showPostCharWaitDialog(String callId, String chars) {
779        if (isVisible()) {
780            final PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
781            fragment.show(getFragmentManager(), "postCharWait");
782
783            mShowPostCharWaitDialogOnResume = false;
784            mShowPostCharWaitDialogCallId = null;
785            mShowPostCharWaitDialogChars = null;
786        } else {
787            mShowPostCharWaitDialogOnResume = true;
788            mShowPostCharWaitDialogCallId = callId;
789            mShowPostCharWaitDialogChars = chars;
790        }
791    }
792
793    @Override
794    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
795        if (mCallCardFragment != null) {
796            mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
797        }
798        return super.dispatchPopulateAccessibilityEvent(event);
799    }
800
801    public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
802        Log.d(this, "maybeShowErrorDialogOnDisconnect");
803
804        if (!isFinishing() && !TextUtils.isEmpty(disconnectCause.getDescription())
805                && (disconnectCause.getCode() == DisconnectCause.ERROR ||
806                disconnectCause.getCode() == DisconnectCause.RESTRICTED)) {
807            showErrorDialog(disconnectCause.getDescription());
808        }
809    }
810
811    public void dismissPendingDialogs() {
812        if (mDialog != null) {
813            mDialog.dismiss();
814            mDialog = null;
815        }
816        if (mAnswerFragment != null) {
817            mAnswerFragment.dismissPendingDialogs();
818        }
819    }
820
821    /**
822     * Utility function to bring up a generic "error" dialog.
823     */
824    private void showErrorDialog(CharSequence msg) {
825        Log.i(this, "Show Dialog: " + msg);
826
827        dismissPendingDialogs();
828
829        mDialog = new AlertDialog.Builder(this)
830                .setMessage(msg)
831                .setPositiveButton(android.R.string.ok, new OnClickListener() {
832                    @Override
833                    public void onClick(DialogInterface dialog, int which) {
834                        onDialogDismissed();
835                    }
836                })
837                .setOnCancelListener(new OnCancelListener() {
838                    @Override
839                    public void onCancel(DialogInterface dialog) {
840                        onDialogDismissed();
841                    }
842                })
843                .create();
844
845        mDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
846        mDialog.show();
847    }
848
849    private void onDialogDismissed() {
850        mDialog = null;
851        CallList.getInstance().onErrorDialogDismissed();
852        InCallPresenter.getInstance().onDismissDialog();
853    }
854
855    public void setExcludeFromRecents(boolean exclude) {
856        ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
857        List<ActivityManager.AppTask> tasks = am.getAppTasks();
858        int taskId = getTaskId();
859        for (int i = 0; i < tasks.size(); i++) {
860            ActivityManager.AppTask task = tasks.get(i);
861            if (task.getTaskInfo().id == taskId) {
862                try {
863                    task.setExcludeFromRecents(exclude);
864                } catch (RuntimeException e) {
865                    Log.e(TAG, "RuntimeException when excluding task from recents.", e);
866                }
867            }
868        }
869    }
870
871
872    public OnTouchListener getDispatchTouchEventListener() {
873        return mDispatchTouchEventListener;
874    }
875
876    public void setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener) {
877        this.mDispatchTouchEventListener = mDispatchTouchEventListener;
878    }
879
880    /**
881     * Enables the OrientationEventListener if enable flag is true. Disables it if enable is
882     * false
883     * @param enable true or false.
884     */
885    public void enableInCallOrientationEventListener(boolean enable) {
886        if (enable) {
887            mInCallOrientationEventListener.enable(enable);
888        } else {
889            mInCallOrientationEventListener.disable();
890        }
891    }
892}
893