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