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.OnCancelListener;
30 import android.content.DialogInterface.OnClickListener;
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.KeyEvent;
41 import android.view.MenuItem;
42 import android.view.MotionEvent;
43 import android.view.OrientationEventListener;
44 import android.view.Surface;
45 import android.view.View;
46 import android.view.View.OnTouchListener;
47 import android.view.Window;
48 import android.view.WindowManager;
49 import android.view.accessibility.AccessibilityEvent;
50 import android.view.animation.Animation;
51 import android.view.animation.AnimationUtils;
52 
53 import com.android.contacts.common.activity.TransactionSafeActivity;
54 import com.android.contacts.common.compat.CompatUtils;
55 import com.android.contacts.common.interactions.TouchPointManager;
56 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
57 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
58 import com.android.dialer.R;
59 import com.android.dialer.logging.Logger;
60 import com.android.dialer.logging.ScreenEvent;
61 import com.android.incallui.Call.State;
62 import com.android.incallui.util.AccessibilityUtil;
63 import com.android.phone.common.animation.AnimUtils;
64 import com.android.phone.common.animation.AnimationListenerAdapter;
65 
66 import java.util.ArrayList;
67 import java.util.List;
68 import java.util.Locale;
69 
70 /**
71  * Main activity that the user interacts with while in a live call.
72  */
73 public 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
onCreate(Bundle icicle)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
onSaveInstanceState(Bundle out)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
onStart()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
onResume()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
onPause()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
onStop()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
onDestroy()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
onFragmentAttached(Fragment fragment)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
onConfigurationChanged(Configuration newConfig)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      */
isVisible()386     /* package */ boolean isVisible() {
387         return isSafeToCommitTransactions();
388     }
389 
hasPendingDialogs()390     private boolean hasPendingDialogs() {
391         return mDialog != null || (mAnswerFragment != null && mAnswerFragment.hasPendingDialogs());
392     }
393 
394     @Override
finish()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
onNewIntent(Intent intent)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
onBackPressed()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
onOptionsItemSelected(MenuItem item)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
onKeyUp(int keyCode, KeyEvent event)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
dispatchTouchEvent(MotionEvent ev)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
onKeyDown(int keyCode, KeyEvent event)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 
handleDialerKeyDown(int keyCode, KeyEvent event)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 
getCallButtonFragment()562     public CallButtonFragment getCallButtonFragment() {
563         return mCallButtonFragment;
564     }
565 
getCallCardFragment()566     public CallCardFragment getCallCardFragment() {
567         return mCallCardFragment;
568     }
569 
getAnswerFragment()570     public AnswerFragment getAnswerFragment() {
571         return mAnswerFragment;
572     }
573 
internalResolveIntent(Intent intent)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      */
relaunchedFromDialer(boolean showDialpad)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 
dismissKeyguard(boolean dismiss)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 
showFragment(String tag, boolean show, boolean executeImmediately)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 
createNewFragmentForTag(String tag)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 
getFragmentManagerForTag(String tag)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 
getScreenTypeForTag(String tag)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 
getContainerIdForFragment(String tag)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      */
showDialpadFragment(boolean show, boolean animate)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 
isDialpadVisible()823     public boolean isDialpadVisible() {
824         return mDialpadFragment != null && mDialpadFragment.isVisible();
825     }
826 
showCallCardFragment(boolean show)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      */
showConferenceFragment(boolean show)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 
showAnswerFragment(boolean show)846     public void showAnswerFragment(boolean show) {
847         showFragment(TAG_ANSWER_FRAGMENT, show, true);
848     }
849 
showPostCharWaitDialog(String callId, String chars)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
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)866     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
867         if (mCallCardFragment != null) {
868             mCallCardFragment.dispatchPopulateAccessibilityEvent(event);
869         }
870         return super.dispatchPopulateAccessibilityEvent(event);
871     }
872 
maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause)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 
dismissPendingDialogs()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      */
showErrorDialog(CharSequence msg)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 
onDialogDismissed()921     private void onDialogDismissed() {
922         mDialog = null;
923         CallList.getInstance().onErrorDialogDismissed();
924         InCallPresenter.getInstance().onDismissDialog();
925     }
926 
setExcludeFromRecents(boolean exclude)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 
getDispatchTouchEventListener()944     public OnTouchListener getDispatchTouchEventListener() {
945         return mDispatchTouchEventListener;
946     }
947 
setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener)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      */
enableInCallOrientationEventListener(boolean enable)957     public void enableInCallOrientationEventListener(boolean enable) {
958         if (enable) {
959             mInCallOrientationEventListener.enable(enable);
960         } else {
961             mInCallOrientationEventListener.disable();
962         }
963     }
964 }
965