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