• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.ActivityManager;
20 import android.app.ActivityManager.AppTask;
21 import android.app.ActivityManager.TaskDescription;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.DialogInterface.OnCancelListener;
27 import android.content.DialogInterface.OnDismissListener;
28 import android.content.Intent;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.os.Bundle;
32 import android.support.annotation.IntDef;
33 import android.support.annotation.NonNull;
34 import android.support.annotation.Nullable;
35 import android.support.v4.app.Fragment;
36 import android.support.v4.app.FragmentManager;
37 import android.support.v4.app.FragmentTransaction;
38 import android.support.v4.content.res.ResourcesCompat;
39 import android.telecom.DisconnectCause;
40 import android.telecom.PhoneAccountHandle;
41 import android.text.TextUtils;
42 import android.util.Pair;
43 import android.view.KeyEvent;
44 import android.view.View;
45 import android.view.Window;
46 import android.view.WindowManager;
47 import android.view.animation.Animation;
48 import android.view.animation.AnimationUtils;
49 import android.widget.CheckBox;
50 import android.widget.Toast;
51 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
52 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
53 import com.android.dialer.animation.AnimUtils;
54 import com.android.dialer.animation.AnimationListenerAdapter;
55 import com.android.dialer.common.LogUtil;
56 import com.android.dialer.compat.CompatUtils;
57 import com.android.dialer.logging.Logger;
58 import com.android.dialer.logging.ScreenEvent;
59 import com.android.dialer.util.ViewUtil;
60 import com.android.incallui.audiomode.AudioModeProvider;
61 import com.android.incallui.call.CallList;
62 import com.android.incallui.call.DialerCall;
63 import com.android.incallui.call.DialerCall.State;
64 import com.android.incallui.call.TelecomAdapter;
65 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
66 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
67 import com.android.incallui.wifi.EnableWifiCallingPrompt;
68 import java.lang.annotation.Retention;
69 import java.lang.annotation.RetentionPolicy;
70 import java.util.ArrayList;
71 import java.util.List;
72 
73 /** Shared functionality between the new and old in call activity. */
74 public class InCallActivityCommon {
75 
76   private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
77   private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
78   private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
79       "InCallActivity.for_full_screen_intent";
80 
81   private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
82 
83   private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
84   private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
85   private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
86 
87   @Retention(RetentionPolicy.SOURCE)
88   @IntDef({
89     DIALPAD_REQUEST_NONE,
90     DIALPAD_REQUEST_SHOW,
91     DIALPAD_REQUEST_HIDE,
92   })
93   @interface DialpadRequestType {}
94 
95   private static final int DIALPAD_REQUEST_NONE = 1;
96   private static final int DIALPAD_REQUEST_SHOW = 2;
97   private static final int DIALPAD_REQUEST_HIDE = 3;
98 
99   private final InCallActivity inCallActivity;
100   private boolean dismissKeyguard;
101   private boolean showPostCharWaitDialogOnResume;
102   private String showPostCharWaitDialogCallId;
103   private String showPostCharWaitDialogChars;
104   private Dialog dialog;
105   private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
106   private InCallOrientationEventListener inCallOrientationEventListener;
107   private Animation dialpadSlideInAnimation;
108   private Animation dialpadSlideOutAnimation;
109   private boolean animateDialpadOnShow;
110   private String dtmfTextToPreopulate;
111   @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
112 
113   private final SelectPhoneAccountListener selectAccountListener =
114       new SelectPhoneAccountListener() {
115         @Override
116         public void onPhoneAccountSelected(
117             PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
118           DialerCall call = CallList.getInstance().getCallById(callId);
119           LogUtil.i(
120               "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
121               "call: " + call);
122           if (call != null) {
123             call.phoneAccountSelected(selectedAccountHandle, setDefault);
124           }
125         }
126 
127         @Override
128         public void onDialogDismissed(String callId) {
129           DialerCall call = CallList.getInstance().getCallById(callId);
130           LogUtil.i(
131               "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
132               "disconnecting call: " + call);
133           if (call != null) {
134             call.disconnect();
135           }
136         }
137       };
138 
139   private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
140       new Callback() {
141         @Override
142         public void continueCall(@NonNull String callId) {
143           LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
144         }
145 
146         @Override
147         public void cancelCall(@NonNull String callId) {
148           DialerCall call = CallList.getInstance().getCallById(callId);
149           if (call == null) {
150             LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
151             return;
152           }
153           LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
154           call.disconnect();
155         }
156       };
157 
setIntentExtras( Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen)158   public static void setIntentExtras(
159       Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
160     if (showDialpad) {
161       intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
162     }
163     intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
164     intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
165   }
166 
InCallActivityCommon(InCallActivity inCallActivity)167   public InCallActivityCommon(InCallActivity inCallActivity) {
168     this.inCallActivity = inCallActivity;
169   }
170 
onCreate(Bundle icicle)171   public void onCreate(Bundle icicle) {
172     // set this flag so this activity will stay in front of the keyguard
173     // Have the WindowManager filter out touch events that are "too fat".
174     int flags =
175         WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
176             | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
177             | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
178 
179     inCallActivity.getWindow().addFlags(flags);
180 
181     inCallActivity.setContentView(R.layout.incall_screen);
182 
183     internalResolveIntent(inCallActivity.getIntent());
184 
185     boolean isLandscape =
186         inCallActivity.getResources().getConfiguration().orientation
187             == Configuration.ORIENTATION_LANDSCAPE;
188     boolean isRtl = ViewUtil.isRtl();
189 
190     if (isLandscape) {
191       dialpadSlideInAnimation =
192           AnimationUtils.loadAnimation(
193               inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
194       dialpadSlideOutAnimation =
195           AnimationUtils.loadAnimation(
196               inCallActivity,
197               isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
198     } else {
199       dialpadSlideInAnimation =
200           AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
201       dialpadSlideOutAnimation =
202           AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
203     }
204 
205     dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
206     dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
207 
208     dialpadSlideOutAnimation.setAnimationListener(
209         new AnimationListenerAdapter() {
210           @Override
211           public void onAnimationEnd(Animation animation) {
212             performHideDialpadFragment();
213           }
214         });
215 
216     if (icicle != null) {
217       // If the dialpad was shown before, set variables indicating it should be shown and
218       // populated with the previous DTMF text.  The dialpad is actually shown and populated
219       // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
220       if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
221         boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
222         showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
223         animateDialpadOnShow = false;
224       }
225       dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
226 
227       SelectPhoneAccountDialogFragment dialogFragment =
228           (SelectPhoneAccountDialogFragment)
229               inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
230       if (dialogFragment != null) {
231         dialogFragment.setListener(selectAccountListener);
232       }
233     }
234 
235     InternationalCallOnWifiDialogFragment existingInternationalFragment =
236         (InternationalCallOnWifiDialogFragment)
237             inCallActivity
238                 .getSupportFragmentManager()
239                 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
240     if (existingInternationalFragment != null) {
241       LogUtil.i(
242           "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
243       existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
244     }
245 
246     inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity);
247   }
248 
onSaveInstanceState(Bundle out)249   public void onSaveInstanceState(Bundle out) {
250     // TODO: The dialpad fragment should handle this as part of its own state
251     out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
252     DialpadFragment dialpadFragment = getDialpadFragment();
253     if (dialpadFragment != null) {
254       out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
255     }
256   }
257 
onStart()258   public void onStart() {
259     // setting activity should be last thing in setup process
260     InCallPresenter.getInstance().setActivity(inCallActivity);
261     enableInCallOrientationEventListener(
262         inCallActivity.getRequestedOrientation()
263             == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
264 
265     InCallPresenter.getInstance().onActivityStarted();
266   }
267 
onResume()268   public void onResume() {
269     if (InCallPresenter.getInstance().isReadyForTearDown()) {
270       LogUtil.i(
271           "InCallActivityCommon.onResume",
272           "InCallPresenter is ready for tear down, not sending updates");
273     } else {
274       updateTaskDescription();
275       InCallPresenter.getInstance().onUiShowing(true);
276     }
277 
278     // If there is a pending request to show or hide the dialpad, handle that now.
279     if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
280       if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
281         // Exit fullscreen so that the user has access to the dialpad hide/show button and
282         // can hide the dialpad.  Important when showing the dialpad from within dialer.
283         InCallPresenter.getInstance().setFullScreen(false, true /* force */);
284 
285         inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
286         animateDialpadOnShow = false;
287 
288         DialpadFragment dialpadFragment = getDialpadFragment();
289         if (dialpadFragment != null) {
290           dialpadFragment.setDtmfText(dtmfTextToPreopulate);
291           dtmfTextToPreopulate = null;
292         }
293       } else {
294         LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
295         if (getDialpadFragment() != null) {
296           inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
297         }
298       }
299       showDialpadRequest = DIALPAD_REQUEST_NONE;
300     }
301 
302     if (showPostCharWaitDialogOnResume) {
303       showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
304     }
305 
306     CallList.getInstance()
307         .onInCallUiShown(
308             inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
309   }
310 
311   // onPause is guaranteed to be called when the InCallActivity goes
312   // in the background.
onPause()313   public void onPause() {
314     DialpadFragment dialpadFragment = getDialpadFragment();
315     if (dialpadFragment != null) {
316       dialpadFragment.onDialerKeyUp(null);
317     }
318 
319     InCallPresenter.getInstance().onUiShowing(false);
320     if (inCallActivity.isFinishing()) {
321       InCallPresenter.getInstance().unsetActivity(inCallActivity);
322     }
323   }
324 
onStop()325   public void onStop() {
326     enableInCallOrientationEventListener(false);
327     InCallPresenter.getInstance().updateIsChangingConfigurations();
328     InCallPresenter.getInstance().onActivityStopped();
329   }
330 
onDestroy()331   public void onDestroy() {
332     InCallPresenter.getInstance().unsetActivity(inCallActivity);
333     InCallPresenter.getInstance().updateIsChangingConfigurations();
334   }
335 
onNewIntent(Intent intent, boolean isRecreating)336   void onNewIntent(Intent intent, boolean isRecreating) {
337     LogUtil.i("InCallActivityCommon.onNewIntent", "");
338 
339     // We're being re-launched with a new Intent.  Since it's possible for a
340     // single InCallActivity instance to persist indefinitely (even if we
341     // finish() ourselves), this sequence can potentially happen any time
342     // the InCallActivity needs to be displayed.
343 
344     // Stash away the new intent so that we can get it in the future
345     // by calling getIntent().  (Otherwise getIntent() will return the
346     // original Intent from when we first got created!)
347     inCallActivity.setIntent(intent);
348 
349     // Activities are always paused before receiving a new intent, so
350     // we can count on our onResume() method being called next.
351 
352     // Just like in onCreate(), handle the intent.
353     // Skip if InCallActivity is going to recreate since this will be called in onCreate().
354     if (!isRecreating) {
355       internalResolveIntent(intent);
356     }
357   }
358 
onBackPressed(boolean isInCallScreenVisible)359   public boolean onBackPressed(boolean isInCallScreenVisible) {
360     LogUtil.i("InCallActivityCommon.onBackPressed", "");
361 
362     // BACK is also used to exit out of any "special modes" of the
363     // in-call UI:
364     if (!inCallActivity.isVisible()) {
365       return true;
366     }
367 
368     if (!isInCallScreenVisible) {
369       return true;
370     }
371 
372     DialpadFragment dialpadFragment = getDialpadFragment();
373     if (dialpadFragment != null && dialpadFragment.isVisible()) {
374       inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
375       return true;
376     }
377 
378     // Always disable the Back key while an incoming call is ringing
379     DialerCall call = CallList.getInstance().getIncomingCall();
380     if (call != null) {
381       LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
382       return true;
383     }
384 
385     // Nothing special to do. Fall back to the default behavior.
386     return false;
387   }
388 
onKeyUp(int keyCode, KeyEvent event)389   public boolean onKeyUp(int keyCode, KeyEvent event) {
390     DialpadFragment dialpadFragment = getDialpadFragment();
391     // push input to the dialer.
392     if (dialpadFragment != null
393         && (dialpadFragment.isVisible())
394         && (dialpadFragment.onDialerKeyUp(event))) {
395       return true;
396     } else if (keyCode == KeyEvent.KEYCODE_CALL) {
397       // Always consume CALL to be sure the PhoneWindow won't do anything with it
398       return true;
399     }
400     return false;
401   }
402 
onKeyDown(int keyCode, KeyEvent event)403   public boolean onKeyDown(int keyCode, KeyEvent event) {
404     switch (keyCode) {
405       case KeyEvent.KEYCODE_CALL:
406         boolean handled = InCallPresenter.getInstance().handleCallKey();
407         if (!handled) {
408           LogUtil.e(
409               "InCallActivityCommon.onKeyDown",
410               "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
411         }
412         // Always consume CALL to be sure the PhoneWindow won't do anything with it
413         return true;
414 
415         // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
416         // The standard system-wide handling of the ENDCALL key
417         // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
418         // already implements exactly what the UI spec wants,
419         // namely (1) "hang up" if there's a current active call,
420         // or (2) "don't answer" if there's a current ringing call.
421 
422       case KeyEvent.KEYCODE_CAMERA:
423         // Disable the CAMERA button while in-call since it's too
424         // easy to press accidentally.
425         return true;
426 
427       case KeyEvent.KEYCODE_VOLUME_UP:
428       case KeyEvent.KEYCODE_VOLUME_DOWN:
429       case KeyEvent.KEYCODE_VOLUME_MUTE:
430         // Ringer silencing handled by PhoneWindowManager.
431         break;
432 
433       case KeyEvent.KEYCODE_MUTE:
434         TelecomAdapter.getInstance()
435             .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
436         return true;
437 
438         // Various testing/debugging features, enabled ONLY when VERBOSE == true.
439       case KeyEvent.KEYCODE_SLASH:
440         if (LogUtil.isVerboseEnabled()) {
441           LogUtil.v(
442               "InCallActivityCommon.onKeyDown",
443               "----------- InCallActivity View dump --------------");
444           // Dump starting from the top-level view of the entire activity:
445           Window w = inCallActivity.getWindow();
446           View decorView = w.getDecorView();
447           LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
448           return true;
449         }
450         break;
451       case KeyEvent.KEYCODE_EQUALS:
452         break;
453       default: // fall out
454     }
455 
456     return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
457   }
458 
handleDialerKeyDown(int keyCode, KeyEvent event)459   private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
460     LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
461 
462     // As soon as the user starts typing valid dialable keys on the
463     // keyboard (presumably to type DTMF tones) we start passing the
464     // key events to the DTMFDialer's onDialerKeyDown.
465     DialpadFragment dialpadFragment = getDialpadFragment();
466     if (dialpadFragment != null && dialpadFragment.isVisible()) {
467       return dialpadFragment.onDialerKeyDown(event);
468     }
469 
470     return false;
471   }
472 
dismissKeyguard(boolean dismiss)473   public void dismissKeyguard(boolean dismiss) {
474     if (dismissKeyguard == dismiss) {
475       return;
476     }
477     dismissKeyguard = dismiss;
478     if (dismiss) {
479       inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
480     } else {
481       inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
482     }
483   }
484 
showPostCharWaitDialog(String callId, String chars)485   public void showPostCharWaitDialog(String callId, String chars) {
486     if (inCallActivity.isVisible()) {
487       PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
488       fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
489 
490       showPostCharWaitDialogOnResume = false;
491       showPostCharWaitDialogCallId = null;
492       showPostCharWaitDialogChars = null;
493     } else {
494       showPostCharWaitDialogOnResume = true;
495       showPostCharWaitDialogCallId = callId;
496       showPostCharWaitDialogChars = chars;
497     }
498   }
499 
maybeShowErrorDialogOnDisconnect(DisconnectCause cause)500   public void maybeShowErrorDialogOnDisconnect(DisconnectCause cause) {
501     LogUtil.i(
502         "InCallActivityCommon.maybeShowErrorDialogOnDisconnect", "disconnect cause: %s", cause);
503 
504     if (!inCallActivity.isFinishing()) {
505       if (EnableWifiCallingPrompt.shouldShowPrompt(cause)) {
506         Pair<Dialog, CharSequence> pair =
507             EnableWifiCallingPrompt.createDialog(inCallActivity, cause);
508         showErrorDialog(pair.first, pair.second);
509       } else if (shouldShowDisconnectErrorDialog(cause)) {
510         Pair<Dialog, CharSequence> pair = getDisconnectErrorDialog(inCallActivity, cause);
511         showErrorDialog(pair.first, pair.second);
512       }
513     }
514   }
515 
516   /**
517    * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
518    * be shown on launch.
519    *
520    * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
521    *     false} to indicate no change should be made to the dialpad visibility.
522    */
relaunchedFromDialer(boolean showDialpad)523   private void relaunchedFromDialer(boolean showDialpad) {
524     showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
525     animateDialpadOnShow = true;
526 
527     if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
528       // If there's only one line in use, AND it's on hold, then we're sure the user
529       // wants to use the dialpad toward the exact line, so un-hold the holding line.
530       DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
531       if (call != null && call.getState() == State.ONHOLD) {
532         call.unhold();
533       }
534     }
535   }
536 
dismissPendingDialogs()537   void dismissPendingDialogs() {
538     if (dialog != null) {
539       dialog.dismiss();
540       dialog = null;
541     }
542     if (selectPhoneAccountDialogFragment != null) {
543       selectPhoneAccountDialogFragment.dismiss();
544       selectPhoneAccountDialogFragment = null;
545     }
546 
547     InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
548         (InternationalCallOnWifiDialogFragment)
549             inCallActivity
550                 .getSupportFragmentManager()
551                 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
552     if (internationalCallOnWifiFragment != null) {
553       LogUtil.i(
554           "InCallActivityCommon.dismissPendingDialogs",
555           "dismissing InternationalCallOnWifiDialogFragment");
556       internationalCallOnWifiFragment.dismiss();
557     }
558   }
559 
shouldShowDisconnectErrorDialog(@onNull DisconnectCause cause)560   private static boolean shouldShowDisconnectErrorDialog(@NonNull DisconnectCause cause) {
561     return !TextUtils.isEmpty(cause.getDescription())
562         && (cause.getCode() == DisconnectCause.ERROR
563             || cause.getCode() == DisconnectCause.RESTRICTED);
564   }
565 
getDisconnectErrorDialog( @onNull Context context, @NonNull DisconnectCause cause)566   private static Pair<Dialog, CharSequence> getDisconnectErrorDialog(
567       @NonNull Context context, @NonNull DisconnectCause cause) {
568     CharSequence message = cause.getDescription();
569     Dialog dialog =
570         new AlertDialog.Builder(context)
571             .setMessage(message)
572             .setPositiveButton(android.R.string.ok, null)
573             .create();
574     return new Pair<>(dialog, message);
575   }
576 
showErrorDialog(Dialog dialog, CharSequence message)577   private void showErrorDialog(Dialog dialog, CharSequence message) {
578     LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
579     inCallActivity.dismissPendingDialogs();
580 
581     // Show toast if apps is in background when dialog won't be visible.
582     if (!inCallActivity.isVisible()) {
583       Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
584       return;
585     }
586 
587     this.dialog = dialog;
588     dialog.setOnDismissListener(
589         new OnDismissListener() {
590           @Override
591           public void onDismiss(DialogInterface dialog) {
592             LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
593             onDialogDismissed();
594           }
595         });
596     dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
597     dialog.show();
598   }
599 
onDialogDismissed()600   private void onDialogDismissed() {
601     dialog = null;
602     CallList.getInstance().onErrorDialogDismissed();
603     InCallPresenter.getInstance().onDismissDialog();
604   }
605 
enableInCallOrientationEventListener(boolean enable)606   public void enableInCallOrientationEventListener(boolean enable) {
607     if (enable) {
608       inCallOrientationEventListener.enable(true);
609     } else {
610       inCallOrientationEventListener.disable();
611     }
612   }
613 
setExcludeFromRecents(boolean exclude)614   public void setExcludeFromRecents(boolean exclude) {
615     List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
616     int taskId = inCallActivity.getTaskId();
617     for (int i = 0; i < tasks.size(); i++) {
618       ActivityManager.AppTask task = tasks.get(i);
619       try {
620         if (task.getTaskInfo().id == taskId) {
621           task.setExcludeFromRecents(exclude);
622         }
623       } catch (RuntimeException e) {
624         LogUtil.e(
625             "InCallActivityCommon.setExcludeFromRecents",
626             "RuntimeException when excluding task from recents.",
627             e);
628       }
629     }
630   }
631 
showInternationalCallOnWifiDialog(@onNull DialerCall call)632   void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
633     LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
634     if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
635       LogUtil.i(
636           "InCallActivityCommon.showInternationalCallOnWifiDialog",
637           "InternationalCallOnWifiDialogFragment.shouldShow returned false");
638       return;
639     }
640 
641     InternationalCallOnWifiDialogFragment fragment =
642         InternationalCallOnWifiDialogFragment.newInstance(
643             call.getId(), internationalCallOnWifiCallback);
644     fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
645   }
646 
showWifiToLteHandoverToast(DialerCall call)647   public void showWifiToLteHandoverToast(DialerCall call) {
648     if (call.hasShownWiFiToLteHandoverToast()) {
649       return;
650     }
651     Toast.makeText(
652             inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
653         .show();
654     call.setHasShownWiFiToLteHandoverToast();
655   }
656 
showWifiFailedDialog(final DialerCall call)657   public void showWifiFailedDialog(final DialerCall call) {
658     if (call.showWifiHandoverAlertAsToast()) {
659       LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
660       Toast.makeText(
661               inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
662           .show();
663       return;
664     }
665 
666     dismissPendingDialogs();
667 
668     AlertDialog.Builder builder =
669         new AlertDialog.Builder(inCallActivity)
670             .setTitle(R.string.video_call_lte_to_wifi_failed_title);
671 
672     // This allows us to use the theme of the dialog instead of the activity
673     View dialogCheckBoxView =
674         View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
675     final CheckBox wifiHandoverFailureCheckbox =
676         (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
677     wifiHandoverFailureCheckbox.setChecked(false);
678 
679     dialog =
680         builder
681             .setView(dialogCheckBoxView)
682             .setMessage(R.string.video_call_lte_to_wifi_failed_message)
683             .setOnCancelListener(
684                 new OnCancelListener() {
685                   @Override
686                   public void onCancel(DialogInterface dialog) {
687                     onDialogDismissed();
688                   }
689                 })
690             .setPositiveButton(
691                 android.R.string.ok,
692                 new DialogInterface.OnClickListener() {
693                   @Override
694                   public void onClick(DialogInterface dialog, int id) {
695                     call.setDoNotShowDialogForHandoffToWifiFailure(
696                         wifiHandoverFailureCheckbox.isChecked());
697                     dialog.cancel();
698                     onDialogDismissed();
699                   }
700                 })
701             .create();
702 
703     LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
704     dialog.show();
705   }
706 
showDialpadFragment(boolean show, boolean animate)707   public boolean showDialpadFragment(boolean show, boolean animate) {
708     // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
709     boolean isDialpadVisible = isDialpadVisible();
710     LogUtil.i(
711         "InCallActivityCommon.showDialpadFragment",
712         "show: %b, animate: %b, " + "isDialpadVisible: %b",
713         show,
714         animate,
715         isDialpadVisible);
716     if (show == isDialpadVisible) {
717       return false;
718     }
719 
720     FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
721     if (dialpadFragmentManager == null) {
722       LogUtil.i(
723           "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
724       return false;
725     }
726 
727     // We don't do a FragmentTransaction on the hide case because it will be dealt with when
728     // the listener is fired after an animation finishes.
729     if (!animate) {
730       if (show) {
731         performShowDialpadFragment(dialpadFragmentManager);
732       } else {
733         performHideDialpadFragment();
734       }
735     } else {
736       if (show) {
737         performShowDialpadFragment(dialpadFragmentManager);
738         getDialpadFragment().animateShowDialpad();
739       }
740       getDialpadFragment()
741           .getView()
742           .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
743     }
744 
745     ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
746     if (sensor != null) {
747       sensor.onDialpadVisible(show);
748     }
749     showDialpadRequest = DIALPAD_REQUEST_NONE;
750     return true;
751   }
752 
performShowDialpadFragment(@onNull FragmentManager dialpadFragmentManager)753   private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
754     FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
755     DialpadFragment dialpadFragment = getDialpadFragment();
756     if (dialpadFragment == null) {
757       transaction.add(
758           inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
759     } else {
760       transaction.show(dialpadFragment);
761     }
762 
763     transaction.commitAllowingStateLoss();
764     dialpadFragmentManager.executePendingTransactions();
765 
766     Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
767   }
768 
performHideDialpadFragment()769   private void performHideDialpadFragment() {
770     FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
771     if (fragmentManager == null) {
772       LogUtil.e(
773           "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
774       return;
775     }
776 
777     Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
778     if (fragment != null) {
779       FragmentTransaction transaction = fragmentManager.beginTransaction();
780       transaction.hide(fragment);
781       transaction.commitAllowingStateLoss();
782       fragmentManager.executePendingTransactions();
783     }
784   }
785 
isDialpadVisible()786   public boolean isDialpadVisible() {
787     DialpadFragment dialpadFragment = getDialpadFragment();
788     return dialpadFragment != null && dialpadFragment.isVisible();
789   }
790 
791   /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
792   @Nullable
getDialpadFragment()793   private DialpadFragment getDialpadFragment() {
794     FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
795     if (fragmentManager == null) {
796       return null;
797     }
798     return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
799   }
800 
updateTaskDescription()801   public void updateTaskDescription() {
802     Resources resources = inCallActivity.getResources();
803     int color;
804     if (resources.getBoolean(R.bool.is_layout_landscape)) {
805       color =
806           ResourcesCompat.getColor(
807               resources, R.color.statusbar_background_color, inCallActivity.getTheme());
808     } else {
809       color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
810     }
811 
812     TaskDescription td =
813         new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
814     inCallActivity.setTaskDescription(td);
815   }
816 
hasPendingDialogs()817   public boolean hasPendingDialogs() {
818     return dialog != null;
819   }
820 
internalResolveIntent(Intent intent)821   private void internalResolveIntent(Intent intent) {
822     if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
823       return;
824     }
825 
826     if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
827       // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
828       // dialpad should be initially visible.  If the extra isn't
829       // present at all, we just leave the dialpad in its previous state.
830       boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
831       LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
832 
833       relaunchedFromDialer(showDialpad);
834     }
835 
836     DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
837     if (outgoingCall == null) {
838       outgoingCall = CallList.getInstance().getPendingOutgoingCall();
839     }
840 
841     if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
842       intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
843 
844       // InCallActivity is responsible for disconnecting a new outgoing call if there
845       // is no way of making it (i.e. no valid call capable accounts).
846       // If the version is not MSIM compatible, then ignore this code.
847       if (CompatUtils.isMSIMCompatible()
848           && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
849         LogUtil.i(
850             "InCallActivityCommon.internalResolveIntent",
851             "call with no valid accounts, disconnecting");
852         outgoingCall.disconnect();
853       }
854 
855       dismissKeyguard(true);
856     }
857 
858     boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
859     if (didShowAccountSelectionDialog) {
860       inCallActivity.hideMainInCallFragment();
861     }
862   }
863 
maybeShowAccountSelectionDialog()864   private boolean maybeShowAccountSelectionDialog() {
865     DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
866     if (waitingForAccountCall == null) {
867       return false;
868     }
869 
870     Bundle extras = waitingForAccountCall.getIntentExtras();
871     List<PhoneAccountHandle> phoneAccountHandles;
872     if (extras != null) {
873       phoneAccountHandles =
874           extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
875     } else {
876       phoneAccountHandles = new ArrayList<>();
877     }
878 
879     selectPhoneAccountDialogFragment =
880         SelectPhoneAccountDialogFragment.newInstance(
881             R.string.select_phone_account_for_calls,
882             true,
883             phoneAccountHandles,
884             selectAccountListener,
885             waitingForAccountCall.getId());
886     selectPhoneAccountDialogFragment.show(
887         inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
888     return true;
889   }
890 }
891