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