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