• 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.app.KeyguardManager;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Configuration;
28 import android.graphics.drawable.GradientDrawable;
29 import android.graphics.drawable.GradientDrawable.Orientation;
30 import android.os.Bundle;
31 import android.os.Trace;
32 import android.support.annotation.ColorInt;
33 import android.support.annotation.FloatRange;
34 import android.support.annotation.IntDef;
35 import android.support.annotation.NonNull;
36 import android.support.annotation.Nullable;
37 import android.support.annotation.VisibleForTesting;
38 import android.support.v4.app.FragmentManager;
39 import android.support.v4.app.FragmentTransaction;
40 import android.support.v4.content.res.ResourcesCompat;
41 import android.support.v4.graphics.ColorUtils;
42 import android.telecom.CallAudioState;
43 import android.telecom.PhoneAccountHandle;
44 import android.telephony.TelephonyManager;
45 import android.view.KeyEvent;
46 import android.view.MenuItem;
47 import android.view.MotionEvent;
48 import android.view.View;
49 import android.view.WindowManager;
50 import android.view.animation.Animation;
51 import android.view.animation.AnimationUtils;
52 import android.widget.CheckBox;
53 import android.widget.Toast;
54 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
55 import com.android.dialer.animation.AnimUtils;
56 import com.android.dialer.animation.AnimationListenerAdapter;
57 import com.android.dialer.common.Assert;
58 import com.android.dialer.common.LogUtil;
59 import com.android.dialer.common.concurrent.ThreadUtil;
60 import com.android.dialer.compat.ActivityCompat;
61 import com.android.dialer.compat.CompatUtils;
62 import com.android.dialer.configprovider.ConfigProviderBindings;
63 import com.android.dialer.logging.Logger;
64 import com.android.dialer.logging.ScreenEvent;
65 import com.android.dialer.metrics.Metrics;
66 import com.android.dialer.metrics.MetricsComponent;
67 import com.android.dialer.util.ViewUtil;
68 import com.android.incallui.answer.bindings.AnswerBindings;
69 import com.android.incallui.answer.protocol.AnswerScreen;
70 import com.android.incallui.answer.protocol.AnswerScreenDelegate;
71 import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
72 import com.android.incallui.answerproximitysensor.PseudoScreenState;
73 import com.android.incallui.audiomode.AudioModeProvider;
74 import com.android.incallui.call.CallList;
75 import com.android.incallui.call.DialerCall;
76 import com.android.incallui.call.DialerCall.State;
77 import com.android.incallui.call.TelecomAdapter;
78 import com.android.incallui.callpending.CallPendingActivity;
79 import com.android.incallui.disconnectdialog.DisconnectMessage;
80 import com.android.incallui.incall.bindings.InCallBindings;
81 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
82 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
83 import com.android.incallui.incall.protocol.InCallScreen;
84 import com.android.incallui.incall.protocol.InCallScreenDelegate;
85 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
86 import com.android.incallui.incalluilock.InCallUiLock;
87 import com.android.incallui.rtt.bindings.RttBindings;
88 import com.android.incallui.rtt.protocol.RttCallScreen;
89 import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
90 import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
91 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
92 import com.android.incallui.video.bindings.VideoBindings;
93 import com.android.incallui.video.protocol.VideoCallScreen;
94 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
95 import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
96 import com.google.common.base.Optional;
97 import java.lang.annotation.Retention;
98 import java.lang.annotation.RetentionPolicy;
99 import java.util.ArrayList;
100 import java.util.List;
101 
102 /** Version of {@link InCallActivity} that shows the new UI */
103 public class InCallActivity extends TransactionSafeFragmentActivity
104     implements AnswerScreenDelegateFactory,
105         InCallScreenDelegateFactory,
106         InCallButtonUiDelegateFactory,
107         VideoCallScreenDelegateFactory,
108         RttCallScreenDelegateFactory,
109         PseudoScreenState.StateChangedListener {
110 
111   @Retention(RetentionPolicy.SOURCE)
112   @IntDef({
113     DIALPAD_REQUEST_NONE,
114     DIALPAD_REQUEST_SHOW,
115     DIALPAD_REQUEST_HIDE,
116   })
117   @interface DialpadRequestType {}
118 
119   private static final int DIALPAD_REQUEST_NONE = 1;
120   private static final int DIALPAD_REQUEST_SHOW = 2;
121   private static final int DIALPAD_REQUEST_HIDE = 3;
122 
123   private static Optional<Integer> audioRouteForTesting = Optional.absent();
124 
125   private final InternationalCallOnWifiCallback internationalCallOnWifiCallback =
126       new InternationalCallOnWifiCallback();
127   private final SelectPhoneAccountListener selectPhoneAccountListener =
128       new SelectPhoneAccountListener();
129 
130   private Animation dialpadSlideInAnimation;
131   private Animation dialpadSlideOutAnimation;
132   private Dialog errorDialog;
133   private GradientDrawable backgroundDrawable;
134   private InCallOrientationEventListener inCallOrientationEventListener;
135   private View pseudoBlackScreenOverlay;
136   private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
137   private String dtmfTextToPrepopulate;
138   private String showPostCharWaitDialogCallId;
139   private String showPostCharWaitDialogChars;
140   private boolean allowOrientationChange;
141   private boolean animateDialpadOnShow;
142   private boolean didShowAnswerScreen;
143   private boolean didShowInCallScreen;
144   private boolean didShowVideoCallScreen;
145   private boolean didShowRttCallScreen;
146   private boolean dismissKeyguard;
147   private boolean isInShowMainInCallFragment;
148   private boolean isRecreating; // whether the activity is going to be recreated
149   private boolean isVisible;
150   private boolean needDismissPendingDialogs;
151   private boolean showPostCharWaitDialogOnResume;
152   private boolean touchDownWhenPseudoScreenOff;
153   private int[] backgroundDrawableColors;
154   @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
155 
getIntent( Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen)156   public static Intent getIntent(
157       Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
158     Intent intent = new Intent(Intent.ACTION_MAIN, null);
159     intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
160     intent.setClass(context, InCallActivity.class);
161     if (showDialpad) {
162       intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true);
163     }
164     intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall);
165     intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen);
166     return intent;
167   }
168 
169   @Override
onResumeFragments()170   protected void onResumeFragments() {
171     super.onResumeFragments();
172     if (needDismissPendingDialogs) {
173       dismissPendingDialogs();
174     }
175   }
176 
177   @Override
onCreate(Bundle bundle)178   protected void onCreate(Bundle bundle) {
179     Trace.beginSection("InCallActivity.onCreate");
180     super.onCreate(bundle);
181 
182     if (bundle != null) {
183       didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
184       didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
185       didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
186       didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN);
187     }
188 
189     setWindowFlags();
190     setContentView(R.layout.incall_screen);
191     internalResolveIntent(getIntent());
192 
193     boolean isLandscape =
194         getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
195     boolean isRtl = ViewUtil.isRtl();
196     if (isLandscape) {
197       dialpadSlideInAnimation =
198           AnimationUtils.loadAnimation(
199               this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
200       dialpadSlideOutAnimation =
201           AnimationUtils.loadAnimation(
202               this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
203     } else {
204       dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
205       dialpadSlideOutAnimation =
206           AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
207     }
208     dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
209     dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
210     dialpadSlideOutAnimation.setAnimationListener(
211         new AnimationListenerAdapter() {
212           @Override
213           public void onAnimationEnd(Animation animation) {
214             hideDialpadFragment();
215           }
216         });
217 
218     if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
219       // If the dialpad was shown before, set related variables so that it can be shown and
220       // populated with the previous DTMF text during onResume().
221       if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) {
222         boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD);
223         showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
224         animateDialpadOnShow = false;
225       }
226       dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT);
227 
228       SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
229           (SelectPhoneAccountDialogFragment)
230               getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT);
231       if (selectPhoneAccountDialogFragment != null) {
232         selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener);
233       }
234     }
235 
236     InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment =
237         (InternationalCallOnWifiDialogFragment)
238             getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
239     if (existingInternationalCallOnWifiDialogFragment != null) {
240       existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback);
241     }
242 
243     inCallOrientationEventListener = new InCallOrientationEventListener(this);
244 
245     getWindow()
246         .getDecorView()
247         .setSystemUiVisibility(
248             View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
249 
250     pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
251     sendBroadcast(CallPendingActivity.getFinishBroadcast());
252     Trace.endSection();
253     MetricsComponent.get(this)
254         .metrics()
255         .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
256     MetricsComponent.get(this)
257         .metrics()
258         .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
259   }
260 
setWindowFlags()261   private void setWindowFlags() {
262     // Allow the activity to be shown when the screen is locked and filter out touch events that are
263     // "too fat".
264     int flags =
265         WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
266             | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
267 
268     // When the audio stream is not via Bluetooth, turn on the screen once the activity is shown.
269     // When the audio stream is via Bluetooth, turn on the screen only for an incoming call.
270     final int audioRoute = getAudioRoute();
271     if (audioRoute != CallAudioState.ROUTE_BLUETOOTH
272         || CallList.getInstance().getIncomingCall() != null) {
273       flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
274     }
275 
276     getWindow().addFlags(flags);
277   }
278 
getAudioRoute()279   private static int getAudioRoute() {
280     if (audioRouteForTesting.isPresent()) {
281       return audioRouteForTesting.get();
282     }
283 
284     return AudioModeProvider.getInstance().getAudioState().getRoute();
285   }
286 
287   @VisibleForTesting(otherwise = VisibleForTesting.NONE)
setAudioRouteForTesting(int audioRoute)288   public static void setAudioRouteForTesting(int audioRoute) {
289     audioRouteForTesting = Optional.of(audioRoute);
290   }
291 
internalResolveIntent(Intent intent)292   private void internalResolveIntent(Intent intent) {
293     if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
294       return;
295     }
296 
297     if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) {
298       // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be
299       // initially visible.  If the extra is absent, leave the dialpad in its previous state.
300       boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false);
301       relaunchedFromDialer(showDialpad);
302     }
303 
304     DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
305     if (outgoingCall == null) {
306       outgoingCall = CallList.getInstance().getPendingOutgoingCall();
307     }
308     if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) {
309       intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL);
310 
311       // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of
312       // making it (i.e. no valid call capable accounts).
313       // If the version is not MSIM compatible, ignore this code.
314       if (CompatUtils.isMSIMCompatible()
315           && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
316         LogUtil.i(
317             "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting");
318         outgoingCall.disconnect();
319       }
320 
321       dismissKeyguard(true);
322     }
323 
324     if (showPhoneAccountSelectionDialog()) {
325       hideMainInCallFragment();
326     }
327   }
328 
329   /**
330    * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
331    * be shown on launch.
332    *
333    * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
334    *     false} to indicate no change should be made to the dialpad visibility.
335    */
relaunchedFromDialer(boolean showDialpad)336   private void relaunchedFromDialer(boolean showDialpad) {
337     showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
338     animateDialpadOnShow = true;
339 
340     if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
341       // If there's only one line in use, AND it's on hold, then we're sure the user
342       // wants to use the dialpad toward the exact line, so un-hold the holding line.
343       DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
344       if (call != null && call.getState() == State.ONHOLD) {
345         call.unhold();
346       }
347     }
348   }
349 
350   /**
351    * Show a phone account selection dialog if there is a call waiting for phone account selection.
352    *
353    * @return true if the dialog was shown.
354    */
showPhoneAccountSelectionDialog()355   private boolean showPhoneAccountSelectionDialog() {
356     DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
357     if (waitingForAccountCall == null) {
358       return false;
359     }
360 
361     Bundle extras = waitingForAccountCall.getIntentExtras();
362     List<PhoneAccountHandle> phoneAccountHandles =
363         extras == null
364             ? new ArrayList<>()
365             : extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
366 
367     selectPhoneAccountDialogFragment =
368         SelectPhoneAccountDialogFragment.newInstance(
369             R.string.select_phone_account_for_calls,
370             true /* canSetDefault */,
371             0 /* setDefaultResId */,
372             phoneAccountHandles,
373             selectPhoneAccountListener,
374             waitingForAccountCall.getId(),
375             null /* hints */);
376     selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
377     return true;
378   }
379 
380   @Override
onSaveInstanceState(Bundle out)381   protected void onSaveInstanceState(Bundle out) {
382     LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
383 
384     // TODO: DialpadFragment should handle this as part of its own state
385     out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible());
386     DialpadFragment dialpadFragment = getDialpadFragment();
387     if (dialpadFragment != null) {
388       out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText());
389     }
390 
391     out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
392     out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
393     out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
394     out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen);
395 
396     super.onSaveInstanceState(out);
397     isVisible = false;
398   }
399 
400   @Override
onStart()401   protected void onStart() {
402     Trace.beginSection("InCallActivity.onStart");
403     super.onStart();
404 
405     isVisible = true;
406     showMainInCallFragment();
407 
408     InCallPresenter.getInstance().setActivity(this);
409     enableInCallOrientationEventListener(
410         getRequestedOrientation()
411             == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
412     InCallPresenter.getInstance().onActivityStarted();
413 
414     if (!isRecreating) {
415       InCallPresenter.getInstance().onUiShowing(true);
416     }
417 
418     if (ActivityCompat.isInMultiWindowMode(this)
419         && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
420       // Hide the dialpad because there may not be enough room
421       showDialpadFragment(false, false);
422     }
423 
424     Trace.endSection();
425   }
426 
427   @Override
onResume()428   protected void onResume() {
429     Trace.beginSection("InCallActivity.onResume");
430     super.onResume();
431 
432     if (!InCallPresenter.getInstance().isReadyForTearDown()) {
433       updateTaskDescription();
434       InCallPresenter.getInstance().updateNotification();
435     }
436 
437     // If there is a pending request to show or hide the dialpad, handle that now.
438     if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
439       if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
440         // Exit fullscreen so that the user has access to the dialpad hide/show button.
441         // This is important when showing the dialpad from within dialer.
442         InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */);
443 
444         showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
445         animateDialpadOnShow = false;
446 
447         DialpadFragment dialpadFragment = getDialpadFragment();
448         if (dialpadFragment != null) {
449           dialpadFragment.setDtmfText(dtmfTextToPrepopulate);
450           dtmfTextToPrepopulate = null;
451         }
452       } else {
453         LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad");
454         if (getDialpadFragment() != null) {
455           showDialpadFragment(false /* show */, false /* animate */);
456         }
457       }
458       showDialpadRequest = DIALPAD_REQUEST_NONE;
459     }
460     updateNavigationBar(isDialpadVisible());
461 
462     if (showPostCharWaitDialogOnResume) {
463       showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
464     }
465 
466     CallList.getInstance()
467         .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false));
468 
469     PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
470     pseudoScreenState.addListener(this);
471     onPseudoScreenStateChanged(pseudoScreenState.isOn());
472     Trace.endSection();
473     // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
474     ThreadUtil.postDelayedOnUiThread(
475         () ->
476             MetricsComponent.get(this)
477                 .metrics()
478                 .recordMemory(Metrics.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME),
479         1000);
480   }
481 
482   @Override
onPause()483   protected void onPause() {
484     Trace.beginSection("InCallActivity.onPause");
485     super.onPause();
486 
487     DialpadFragment dialpadFragment = getDialpadFragment();
488     if (dialpadFragment != null) {
489       dialpadFragment.onDialerKeyUp(null);
490     }
491 
492     InCallPresenter.getInstance().updateNotification();
493 
494     InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
495     Trace.endSection();
496   }
497 
498   @Override
onStop()499   protected void onStop() {
500     Trace.beginSection("InCallActivity.onStop");
501     isVisible = false;
502     super.onStop();
503 
504     // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
505     // user presses the home button).
506     // Without this the pending call will get stuck on phone account selection and new calls can't
507     // be created.
508     // Skip this when the screen is locked since the activity may complete its current life cycle
509     // and restart.
510     if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
511       DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
512       if (waitingForAccountCall != null) {
513         waitingForAccountCall.disconnect();
514       }
515     }
516 
517     enableInCallOrientationEventListener(false);
518     InCallPresenter.getInstance().updateIsChangingConfigurations();
519     InCallPresenter.getInstance().onActivityStopped();
520     if (!isRecreating) {
521       InCallPresenter.getInstance().onUiShowing(false);
522       if (errorDialog != null) {
523         errorDialog.dismiss();
524       }
525     }
526 
527     if (isFinishing()) {
528       InCallPresenter.getInstance().unsetActivity(this);
529     }
530 
531     Trace.endSection();
532   }
533 
534   @Override
onDestroy()535   protected void onDestroy() {
536     Trace.beginSection("InCallActivity.onDestroy");
537     super.onDestroy();
538 
539     InCallPresenter.getInstance().unsetActivity(this);
540     InCallPresenter.getInstance().updateIsChangingConfigurations();
541     Trace.endSection();
542   }
543 
544   @Override
finish()545   public void finish() {
546     if (shouldCloseActivityOnFinish()) {
547       // When user select incall ui from recents after the call is disconnected, it tries to launch
548       // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
549       // crash.
550       // By calling finishAndRemoveTask() instead of finish() the task associated with
551       // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
552       // this case.
553       //
554       // Calling finish won't clear the task and normally when an activity finishes it shouldn't
555       // clear the task since there could be parent activity in the same task that's still alive.
556       // But InCallActivity is special since it's singleInstance which means it's root activity and
557       // only instance of activity in the task. So it should be safe to also remove task when
558       // finishing.
559       // It's also necessary in the sense of it's excluded from recents. So whenever the activity
560       // finishes, the task should also be removed since it doesn't make sense to go back to it in
561       // anyway anymore.
562       super.finishAndRemoveTask();
563     }
564   }
565 
shouldCloseActivityOnFinish()566   private boolean shouldCloseActivityOnFinish() {
567     if (!isVisible) {
568       LogUtil.i(
569           "InCallActivity.shouldCloseActivityOnFinish",
570           "allowing activity to be closed because it's not visible");
571       return true;
572     }
573 
574     if (InCallPresenter.getInstance().isInCallUiLocked()) {
575       LogUtil.i(
576           "InCallActivity.shouldCloseActivityOnFinish",
577           "in call ui is locked, not closing activity");
578       return false;
579     }
580 
581     LogUtil.i(
582         "InCallActivity.shouldCloseActivityOnFinish",
583         "activity is visible and has no locks, allowing activity to close");
584     return true;
585   }
586 
587   @Override
onNewIntent(Intent intent)588   protected void onNewIntent(Intent intent) {
589     LogUtil.enterBlock("InCallActivity.onNewIntent");
590 
591     // If the screen is off, we need to make sure it gets turned on for incoming calls.
592     // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
593     // when the activity is first created. Therefore, to ensure the screen is turned on
594     // for the call waiting case, we recreate() the current activity. There should be no jank from
595     // this since the screen is already off and will remain so until our new activity is up.
596     if (!isVisible) {
597       onNewIntent(intent, true /* isRecreating */);
598       LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
599       recreate();
600     } else {
601       onNewIntent(intent, false /* isRecreating */);
602     }
603   }
604 
605   @VisibleForTesting
onNewIntent(Intent intent, boolean isRecreating)606   void onNewIntent(Intent intent, boolean isRecreating) {
607     this.isRecreating = isRecreating;
608 
609     // We're being re-launched with a new Intent.  Since it's possible for a single InCallActivity
610     // instance to persist indefinitely (even if we finish() ourselves), this sequence can
611     // happen any time the InCallActivity needs to be displayed.
612 
613     // Stash away the new intent so that we can get it in the future by calling getIntent().
614     // Otherwise getIntent() will return the original Intent from when we first got created.
615     setIntent(intent);
616 
617     // Activities are always paused before receiving a new intent, so we can count on our onResume()
618     // method being called next.
619 
620     // Just like in onCreate(), handle the intent.
621     // Skip if InCallActivity is going to be recreated since this will be called in onCreate().
622     if (!isRecreating) {
623       internalResolveIntent(intent);
624     }
625   }
626 
627   @Override
onBackPressed()628   public void onBackPressed() {
629     LogUtil.enterBlock("InCallActivity.onBackPressed");
630 
631     if (!isVisible) {
632       return;
633     }
634 
635     if (!getCallCardFragmentVisible()) {
636       return;
637     }
638 
639     DialpadFragment dialpadFragment = getDialpadFragment();
640     if (dialpadFragment != null && dialpadFragment.isVisible()) {
641       showDialpadFragment(false /* show */, true /* animate */);
642       return;
643     }
644 
645     if (CallList.getInstance().getIncomingCall() != null) {
646       LogUtil.i(
647           "InCallActivity.onBackPressed",
648           "Ignore the press of the back key when an incoming call is ringing");
649       return;
650     }
651 
652     // Nothing special to do. Fall back to the default behavior.
653     super.onBackPressed();
654   }
655 
656   @Override
onOptionsItemSelected(MenuItem item)657   public boolean onOptionsItemSelected(MenuItem item) {
658     LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
659     if (item.getItemId() == android.R.id.home) {
660       onBackPressed();
661       return true;
662     }
663     return super.onOptionsItemSelected(item);
664   }
665 
666   @Override
onKeyUp(int keyCode, KeyEvent event)667   public boolean onKeyUp(int keyCode, KeyEvent event) {
668     DialpadFragment dialpadFragment = getDialpadFragment();
669     if (dialpadFragment != null
670         && dialpadFragment.isVisible()
671         && dialpadFragment.onDialerKeyUp(event)) {
672       return true;
673     }
674 
675     if (keyCode == KeyEvent.KEYCODE_CALL) {
676       // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
677       return true;
678     }
679 
680     return super.onKeyUp(keyCode, event);
681   }
682 
683   @Override
onKeyDown(int keyCode, KeyEvent event)684   public boolean onKeyDown(int keyCode, KeyEvent event) {
685     switch (keyCode) {
686       case KeyEvent.KEYCODE_CALL:
687         if (!InCallPresenter.getInstance().handleCallKey()) {
688           LogUtil.e(
689               "InCallActivity.onKeyDown",
690               "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
691         }
692         // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
693         return true;
694 
695         // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
696         // is exactly what's needed, namely
697         // (1) "hang up" if there's an active call, or
698         // (2) "don't answer" if there's an incoming call.
699         // (See PhoneWindowManager for implementation details.)
700 
701       case KeyEvent.KEYCODE_CAMERA:
702         // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
703         return true;
704 
705       case KeyEvent.KEYCODE_VOLUME_UP:
706       case KeyEvent.KEYCODE_VOLUME_DOWN:
707       case KeyEvent.KEYCODE_VOLUME_MUTE:
708         // Ringer silencing handled by PhoneWindowManager.
709         break;
710 
711       case KeyEvent.KEYCODE_MUTE:
712         TelecomAdapter.getInstance()
713             .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
714         return true;
715 
716       case KeyEvent.KEYCODE_SLASH:
717         // When verbose logging is enabled, dump the view for debugging/testing purposes.
718         if (LogUtil.isVerboseEnabled()) {
719           View decorView = getWindow().getDecorView();
720           LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
721           return true;
722         }
723         break;
724 
725       case KeyEvent.KEYCODE_EQUALS:
726         break;
727 
728       default: // fall out
729     }
730 
731     // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
732     // in DTMF (Dual-tone multi-frequency signaling) code.
733     DialpadFragment dialpadFragment = getDialpadFragment();
734     if (dialpadFragment != null
735         && dialpadFragment.isVisible()
736         && dialpadFragment.onDialerKeyDown(event)) {
737       return true;
738     }
739 
740     return super.onKeyDown(keyCode, event);
741   }
742 
isInCallScreenAnimating()743   public boolean isInCallScreenAnimating() {
744     return false;
745   }
746 
showConferenceFragment(boolean show)747   public void showConferenceFragment(boolean show) {
748     if (show) {
749       startActivity(new Intent(this, ManageConferenceActivity.class));
750     }
751   }
752 
showDialpadFragment(boolean show, boolean animate)753   public void showDialpadFragment(boolean show, boolean animate) {
754     if (show == isDialpadVisible()) {
755       return;
756     }
757 
758     FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
759     if (dialpadFragmentManager == null) {
760       LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager");
761       return;
762     }
763 
764     if (!animate) {
765       if (show) {
766         showDialpadFragment();
767       } else {
768         hideDialpadFragment();
769       }
770     } else {
771       if (show) {
772         showDialpadFragment();
773         getDialpadFragment().animateShowDialpad();
774       }
775       getDialpadFragment()
776           .getView()
777           .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
778     }
779 
780     ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
781     if (sensor != null) {
782       sensor.onDialpadVisible(show);
783     }
784     showDialpadRequest = DIALPAD_REQUEST_NONE;
785 
786     // Note:  onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
787     // repositions itself.
788     getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
789   }
790 
showDialpadFragment()791   private void showDialpadFragment() {
792     FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
793     if (dialpadFragmentManager == null) {
794       return;
795     }
796 
797     FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
798     DialpadFragment dialpadFragment = getDialpadFragment();
799     if (dialpadFragment == null) {
800       transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT);
801     } else {
802       transaction.show(dialpadFragment);
803       dialpadFragment.setUserVisibleHint(true);
804     }
805     transaction.commitAllowingStateLoss();
806     dialpadFragmentManager.executePendingTransactions();
807 
808     Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this);
809     updateNavigationBar(true /* isDialpadVisible */);
810   }
811 
hideDialpadFragment()812   private void hideDialpadFragment() {
813     FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
814     if (dialpadFragmentManager == null) {
815       return;
816     }
817 
818     DialpadFragment dialpadFragment = getDialpadFragment();
819     if (dialpadFragment != null) {
820       FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
821       transaction.hide(dialpadFragment);
822       transaction.commitAllowingStateLoss();
823       dialpadFragmentManager.executePendingTransactions();
824       dialpadFragment.setUserVisibleHint(false);
825     }
826     updateNavigationBar(false /* isDialpadVisible */);
827   }
828 
isDialpadVisible()829   public boolean isDialpadVisible() {
830     DialpadFragment dialpadFragment = getDialpadFragment();
831     return dialpadFragment != null
832         && dialpadFragment.isAdded()
833         && !dialpadFragment.isHidden()
834         && dialpadFragment.getView() != null
835         && dialpadFragment.getUserVisibleHint();
836   }
837 
838   /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
839   @Nullable
getDialpadFragment()840   private DialpadFragment getDialpadFragment() {
841     FragmentManager fragmentManager = getDialpadFragmentManager();
842     if (fragmentManager == null) {
843       return null;
844     }
845     return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
846   }
847 
onForegroundCallChanged(DialerCall newForegroundCall)848   public void onForegroundCallChanged(DialerCall newForegroundCall) {
849     updateTaskDescription();
850 
851     if (newForegroundCall == null || !didShowAnswerScreen) {
852       LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
853       updateWindowBackgroundColor(0 /* progress */);
854     }
855   }
856 
updateTaskDescription()857   private void updateTaskDescription() {
858     int color =
859         getResources().getBoolean(R.bool.is_layout_landscape)
860             ? ResourcesCompat.getColor(
861                 getResources(), R.color.statusbar_background_color, getTheme())
862             : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
863     setTaskDescription(
864         new TaskDescription(
865             getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
866   }
867 
updateWindowBackgroundColor(@loatRangefrom = -1f, to = 1.0f) float progress)868   public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
869     ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
870     @ColorInt int top;
871     @ColorInt int middle;
872     @ColorInt int bottom;
873     @ColorInt int gray = 0x66000000;
874 
875     if (ActivityCompat.isInMultiWindowMode(this)) {
876       top = themeColorManager.getBackgroundColorSolid();
877       middle = themeColorManager.getBackgroundColorSolid();
878       bottom = themeColorManager.getBackgroundColorSolid();
879     } else {
880       top = themeColorManager.getBackgroundColorTop();
881       middle = themeColorManager.getBackgroundColorMiddle();
882       bottom = themeColorManager.getBackgroundColorBottom();
883     }
884 
885     if (progress < 0) {
886       float correctedProgress = Math.abs(progress);
887       top = ColorUtils.blendARGB(top, gray, correctedProgress);
888       middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
889       bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
890     }
891 
892     boolean backgroundDirty = false;
893     if (backgroundDrawable == null) {
894       backgroundDrawableColors = new int[] {top, middle, bottom};
895       backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
896       backgroundDirty = true;
897     } else {
898       if (backgroundDrawableColors[0] != top) {
899         backgroundDrawableColors[0] = top;
900         backgroundDirty = true;
901       }
902       if (backgroundDrawableColors[1] != middle) {
903         backgroundDrawableColors[1] = middle;
904         backgroundDirty = true;
905       }
906       if (backgroundDrawableColors[2] != bottom) {
907         backgroundDrawableColors[2] = bottom;
908         backgroundDirty = true;
909       }
910       if (backgroundDirty) {
911         backgroundDrawable.setColors(backgroundDrawableColors);
912       }
913     }
914 
915     if (backgroundDirty) {
916       getWindow().setBackgroundDrawable(backgroundDrawable);
917     }
918   }
919 
isVisible()920   public boolean isVisible() {
921     return isVisible;
922   }
923 
getCallCardFragmentVisible()924   public boolean getCallCardFragmentVisible() {
925     return didShowInCallScreen || didShowVideoCallScreen;
926   }
927 
dismissKeyguard(boolean dismiss)928   public void dismissKeyguard(boolean dismiss) {
929     if (dismissKeyguard == dismiss) {
930       return;
931     }
932 
933     dismissKeyguard = dismiss;
934     if (dismiss) {
935       getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
936     } else {
937       getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
938     }
939   }
940 
showDialogForPostCharWait(String callId, String chars)941   public void showDialogForPostCharWait(String callId, String chars) {
942     if (isVisible) {
943       PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
944       fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT);
945 
946       showPostCharWaitDialogOnResume = false;
947       showPostCharWaitDialogCallId = null;
948       showPostCharWaitDialogChars = null;
949     } else {
950       showPostCharWaitDialogOnResume = true;
951       showPostCharWaitDialogCallId = callId;
952       showPostCharWaitDialogChars = chars;
953     }
954   }
955 
showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage)956   public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
957     LogUtil.i(
958         "InCallActivity.showDialogOrToastForDisconnectedCall",
959         "disconnect cause: %s",
960         disconnectMessage);
961 
962     if (disconnectMessage.dialog == null || isFinishing()) {
963       return;
964     }
965 
966     dismissPendingDialogs();
967 
968     // Show a toast if the app is in background when a dialog can't be visible.
969     if (!isVisible()) {
970       Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
971           .show();
972       return;
973     }
974 
975     // Show the dialog.
976     errorDialog = disconnectMessage.dialog;
977     InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
978     disconnectMessage.dialog.setOnDismissListener(
979         dialogInterface -> {
980           lock.release();
981           onDialogDismissed();
982         });
983     disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
984     disconnectMessage.dialog.show();
985   }
986 
onDialogDismissed()987   private void onDialogDismissed() {
988     errorDialog = null;
989     CallList.getInstance().onErrorDialogDismissed();
990   }
991 
dismissPendingDialogs()992   public void dismissPendingDialogs() {
993     LogUtil.enterBlock("InCallActivity.dismissPendingDialogs");
994 
995     if (!isVisible) {
996       // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
997       // been called.
998       LogUtil.i(
999           "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
1000       needDismissPendingDialogs = true;
1001       return;
1002     }
1003 
1004     // Dismiss the error dialog
1005     if (errorDialog != null) {
1006       errorDialog.dismiss();
1007       errorDialog = null;
1008     }
1009 
1010     // Dismiss the phone account selection dialog
1011     if (selectPhoneAccountDialogFragment != null) {
1012       selectPhoneAccountDialogFragment.dismiss();
1013       selectPhoneAccountDialogFragment = null;
1014     }
1015 
1016     // Dismiss the dialog for international call on WiFi
1017     InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
1018         (InternationalCallOnWifiDialogFragment)
1019             getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
1020     if (internationalCallOnWifiFragment != null) {
1021       internationalCallOnWifiFragment.dismiss();
1022     }
1023 
1024     // Dismiss the answer screen
1025     AnswerScreen answerScreen = getAnswerScreen();
1026     if (answerScreen != null) {
1027       answerScreen.dismissPendingDialogs();
1028     }
1029 
1030     needDismissPendingDialogs = false;
1031   }
1032 
enableInCallOrientationEventListener(boolean enable)1033   private void enableInCallOrientationEventListener(boolean enable) {
1034     if (enable) {
1035       inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
1036     } else {
1037       inCallOrientationEventListener.disable();
1038     }
1039   }
1040 
setExcludeFromRecents(boolean exclude)1041   public void setExcludeFromRecents(boolean exclude) {
1042     int taskId = getTaskId();
1043 
1044     List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks();
1045     for (AppTask task : tasks) {
1046       try {
1047         if (task.getTaskInfo().id == taskId) {
1048           task.setExcludeFromRecents(exclude);
1049         }
1050       } catch (RuntimeException e) {
1051         LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e);
1052       }
1053     }
1054   }
1055 
1056   @Nullable
getDialpadFragmentManager()1057   public FragmentManager getDialpadFragmentManager() {
1058     InCallScreen inCallScreen = getInCallScreen();
1059     if (inCallScreen != null) {
1060       return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
1061     }
1062     return null;
1063   }
1064 
getDialpadContainerId()1065   public int getDialpadContainerId() {
1066     return getInCallScreen().getAnswerAndDialpadContainerResourceId();
1067   }
1068 
1069   @Override
newAnswerScreenDelegate(AnswerScreen answerScreen)1070   public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
1071     DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
1072     if (call == null) {
1073       // This is a work around for a bug where we attempt to create a new delegate after the call
1074       // has already been removed. An example of when this can happen is:
1075       // 1. incoming video call in landscape mode
1076       // 2. remote party hangs up
1077       // 3. activity switches from landscape to portrait
1078       // At step #3 the answer fragment will try to create a new answer delegate but the call won't
1079       // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
1080       // because this new state is transient and the activity will be destroyed soon.
1081       LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
1082       return new AnswerScreenPresenterStub();
1083     } else {
1084       return new AnswerScreenPresenter(
1085           this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
1086     }
1087   }
1088 
1089   @Override
newInCallScreenDelegate()1090   public InCallScreenDelegate newInCallScreenDelegate() {
1091     return new CallCardPresenter(this);
1092   }
1093 
1094   @Override
newInCallButtonUiDelegate()1095   public InCallButtonUiDelegate newInCallButtonUiDelegate() {
1096     return new CallButtonPresenter(this);
1097   }
1098 
1099   @Override
newVideoCallScreenDelegate(VideoCallScreen videoCallScreen)1100   public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
1101     DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
1102     if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
1103       return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
1104     }
1105     return new VideoCallPresenter();
1106   }
1107 
onPrimaryCallStateChanged()1108   public void onPrimaryCallStateChanged() {
1109     Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
1110     showMainInCallFragment();
1111     Trace.endSection();
1112   }
1113 
showToastForWiFiToLteHandover(DialerCall call)1114   public void showToastForWiFiToLteHandover(DialerCall call) {
1115     if (call.hasShownWiFiToLteHandoverToast()) {
1116       return;
1117     }
1118 
1119     Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
1120     call.setHasShownWiFiToLteHandoverToast();
1121   }
1122 
showDialogOrToastForWifiHandoverFailure(DialerCall call)1123   public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
1124     if (call.showWifiHandoverAlertAsToast()) {
1125       Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
1126           .show();
1127       return;
1128     }
1129 
1130     dismissPendingDialogs();
1131 
1132     AlertDialog.Builder builder =
1133         new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
1134 
1135     // This allows us to use the theme of the dialog instead of the activity
1136     View dialogCheckBoxView =
1137         View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
1138     CheckBox wifiHandoverFailureCheckbox =
1139         (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
1140     wifiHandoverFailureCheckbox.setChecked(false);
1141 
1142     InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
1143     errorDialog =
1144         builder
1145             .setView(dialogCheckBoxView)
1146             .setMessage(R.string.video_call_lte_to_wifi_failed_message)
1147             .setOnCancelListener(dialogInterface -> onDialogDismissed())
1148             .setPositiveButton(
1149                 android.R.string.ok,
1150                 (dialogInterface, id) -> {
1151                   call.setDoNotShowDialogForHandoffToWifiFailure(
1152                       wifiHandoverFailureCheckbox.isChecked());
1153                   dialogInterface.cancel();
1154                   onDialogDismissed();
1155                 })
1156             .setOnDismissListener(dialogInterface -> lock.release())
1157             .create();
1158     errorDialog.show();
1159   }
1160 
showDialogForInternationalCallOnWifi(@onNull DialerCall call)1161   public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
1162     if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
1163       LogUtil.i(
1164           "InCallActivity.showDialogForInternationalCallOnWifi",
1165           "InternationalCallOnWifiDialogFragment.shouldShow returned false");
1166       return;
1167     }
1168 
1169     InternationalCallOnWifiDialogFragment fragment =
1170         InternationalCallOnWifiDialogFragment.newInstance(
1171             call.getId(), internationalCallOnWifiCallback);
1172     fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI);
1173   }
1174 
1175   @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode)1176   public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
1177     super.onMultiWindowModeChanged(isInMultiWindowMode);
1178     updateNavigationBar(isDialpadVisible());
1179   }
1180 
updateNavigationBar(boolean isDialpadVisible)1181   private void updateNavigationBar(boolean isDialpadVisible) {
1182     if (ActivityCompat.isInMultiWindowMode(this)) {
1183       return;
1184     }
1185 
1186     View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
1187     if (navigationBarBackground != null) {
1188       navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
1189     }
1190   }
1191 
setAllowOrientationChange(boolean allowOrientationChange)1192   public void setAllowOrientationChange(boolean allowOrientationChange) {
1193     if (this.allowOrientationChange == allowOrientationChange) {
1194       return;
1195     }
1196     this.allowOrientationChange = allowOrientationChange;
1197     if (!allowOrientationChange) {
1198       setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
1199     } else {
1200       setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
1201     }
1202     enableInCallOrientationEventListener(allowOrientationChange);
1203   }
1204 
hideMainInCallFragment()1205   public void hideMainInCallFragment() {
1206     LogUtil.enterBlock("InCallActivity.hideMainInCallFragment");
1207     if (getCallCardFragmentVisible()) {
1208       FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1209       hideInCallScreenFragment(transaction);
1210       hideVideoCallScreenFragment(transaction);
1211       transaction.commitAllowingStateLoss();
1212       getSupportFragmentManager().executePendingTransactions();
1213     }
1214   }
1215 
showMainInCallFragment()1216   private void showMainInCallFragment() {
1217     Trace.beginSection("InCallActivity.showMainInCallFragment");
1218     // If the activity's onStart method hasn't been called yet then defer doing any work.
1219     if (!isVisible) {
1220       LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
1221       Trace.endSection();
1222       return;
1223     }
1224 
1225     // Don't let this be reentrant.
1226     if (isInShowMainInCallFragment) {
1227       LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
1228       Trace.endSection();
1229       return;
1230     }
1231 
1232     isInShowMainInCallFragment = true;
1233     ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
1234     ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
1235     ShouldShowUiResult shouldShowRttUi = getShouldShowRttUi();
1236     LogUtil.i(
1237         "InCallActivity.showMainInCallFragment",
1238         "shouldShowAnswerUi: %b, shouldShowRttUi: %b, shouldShowVideoUi: %b "
1239             + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowRttCallScreen: %b, "
1240             + "didShowVideoCallScreen: %b",
1241         shouldShowAnswerUi.shouldShow,
1242         shouldShowRttUi.shouldShow,
1243         shouldShowVideoUi.shouldShow,
1244         didShowAnswerScreen,
1245         didShowInCallScreen,
1246         didShowRttCallScreen,
1247         didShowVideoCallScreen);
1248     // Only video call ui allows orientation change.
1249     setAllowOrientationChange(shouldShowVideoUi.shouldShow);
1250 
1251     FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1252     boolean didChange;
1253     if (shouldShowAnswerUi.shouldShow) {
1254       didChange = hideInCallScreenFragment(transaction);
1255       didChange |= hideVideoCallScreenFragment(transaction);
1256       didChange |= hideRttCallScreenFragment(transaction);
1257       didChange |= showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
1258     } else if (shouldShowVideoUi.shouldShow) {
1259       didChange = hideInCallScreenFragment(transaction);
1260       didChange |= showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
1261       didChange |= hideRttCallScreenFragment(transaction);
1262       didChange |= hideAnswerScreenFragment(transaction);
1263     } else if (shouldShowRttUi.shouldShow) {
1264       didChange = hideInCallScreenFragment(transaction);
1265       didChange |= hideVideoCallScreenFragment(transaction);
1266       didChange |= hideAnswerScreenFragment(transaction);
1267       didChange |= showRttCallScreenFragment(transaction, shouldShowRttUi.call);
1268     } else {
1269       didChange = showInCallScreenFragment(transaction);
1270       didChange |= hideVideoCallScreenFragment(transaction);
1271       didChange |= hideRttCallScreenFragment(transaction);
1272       didChange |= hideAnswerScreenFragment(transaction);
1273     }
1274 
1275     if (didChange) {
1276       Trace.beginSection("InCallActivity.commitTransaction");
1277       transaction.commitNow();
1278       Trace.endSection();
1279       Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1280     }
1281     isInShowMainInCallFragment = false;
1282     Trace.endSection();
1283   }
1284 
getShouldShowAnswerUi()1285   private ShouldShowUiResult getShouldShowAnswerUi() {
1286     DialerCall call = CallList.getInstance().getIncomingCall();
1287     if (call != null) {
1288       LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
1289       return new ShouldShowUiResult(true, call);
1290     }
1291 
1292     call = CallList.getInstance().getVideoUpgradeRequestCall();
1293     if (call != null) {
1294       LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
1295       return new ShouldShowUiResult(true, call);
1296     }
1297 
1298     // Check if we're showing the answer screen and the call is disconnected. If this condition is
1299     // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
1300     // the user rejects an incoming call.
1301     call = CallList.getInstance().getFirstCall();
1302     if (call == null) {
1303       call = CallList.getInstance().getBackgroundCall();
1304     }
1305     if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
1306       LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
1307       return new ShouldShowUiResult(true, call);
1308     }
1309 
1310     return new ShouldShowUiResult(false, null);
1311   }
1312 
getShouldShowVideoUi()1313   private static ShouldShowUiResult getShouldShowVideoUi() {
1314     DialerCall call = CallList.getInstance().getFirstCall();
1315     if (call == null) {
1316       LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
1317       return new ShouldShowUiResult(false, null);
1318     }
1319 
1320     if (call.isVideoCall()) {
1321       LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
1322       return new ShouldShowUiResult(true, call);
1323     }
1324 
1325     if (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()) {
1326       LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
1327       return new ShouldShowUiResult(true, call);
1328     }
1329 
1330     return new ShouldShowUiResult(false, null);
1331   }
1332 
getShouldShowRttUi()1333   private static ShouldShowUiResult getShouldShowRttUi() {
1334     DialerCall call = CallList.getInstance().getFirstCall();
1335     if (call == null) {
1336       LogUtil.i("InCallActivity.getShouldShowRttUi", "null call");
1337       return new ShouldShowUiResult(false, null);
1338     }
1339 
1340     if (call.isRttCall()) {
1341       LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call");
1342       return new ShouldShowUiResult(true, call);
1343     }
1344 
1345     if (call.hasSentRttUpgradeRequest()) {
1346       LogUtil.i("InCallActivity.getShouldShowRttUi", "upgrading to rtt");
1347       return new ShouldShowUiResult(true, call);
1348     }
1349 
1350     return new ShouldShowUiResult(false, null);
1351   }
1352 
showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call)1353   private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
1354     // When rejecting a call the active call can become null in which case we should continue
1355     // showing the answer screen.
1356     if (didShowAnswerScreen && call == null) {
1357       return false;
1358     }
1359 
1360     Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
1361 
1362     boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
1363 
1364     // Check if we're already showing an answer screen for this call.
1365     if (didShowAnswerScreen) {
1366       AnswerScreen answerScreen = getAnswerScreen();
1367       if (answerScreen.getCallId().equals(call.getId())
1368           && answerScreen.isVideoCall() == call.isVideoCall()
1369           && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
1370           && !answerScreen.isActionTimeout()) {
1371         LogUtil.d(
1372             "InCallActivity.showAnswerScreenFragment",
1373             "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
1374         return false;
1375       }
1376       if (answerScreen.isActionTimeout()) {
1377         LogUtil.i(
1378             "InCallActivity.showAnswerScreenFragment",
1379             "answer fragment exists but has been accepted/rejected and timed out");
1380       } else {
1381         LogUtil.i(
1382             "InCallActivity.showAnswerScreenFragment",
1383             "answer fragment exists but arguments do not match");
1384       }
1385       hideAnswerScreenFragment(transaction);
1386     }
1387 
1388     // Show a new answer screen.
1389     AnswerScreen answerScreen =
1390         AnswerBindings.createAnswerScreen(
1391             call.getId(),
1392             call.isRttCall(),
1393             call.isVideoCall(),
1394             isVideoUpgradeRequest,
1395             call.getVideoTech().isSelfManagedCamera(),
1396             shouldAllowAnswerAndRelease(call),
1397             CallList.getInstance().getBackgroundCall() != null);
1398     transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
1399 
1400     Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
1401     didShowAnswerScreen = true;
1402     return true;
1403   }
1404 
shouldAllowAnswerAndRelease(DialerCall call)1405   private boolean shouldAllowAnswerAndRelease(DialerCall call) {
1406     if (CallList.getInstance().getActiveCall() == null) {
1407       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
1408       return false;
1409     }
1410     if (getSystemService(TelephonyManager.class).getPhoneType()
1411         == TelephonyManager.PHONE_TYPE_CDMA) {
1412       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
1413       return false;
1414     }
1415     if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
1416       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
1417       return false;
1418     }
1419     if (!ConfigProviderBindings.get(this)
1420         .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) {
1421       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
1422       return false;
1423     }
1424 
1425     return true;
1426   }
1427 
hideAnswerScreenFragment(FragmentTransaction transaction)1428   private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
1429     if (!didShowAnswerScreen) {
1430       return false;
1431     }
1432     AnswerScreen answerScreen = getAnswerScreen();
1433     if (answerScreen != null) {
1434       transaction.remove(answerScreen.getAnswerScreenFragment());
1435     }
1436 
1437     didShowAnswerScreen = false;
1438     return true;
1439   }
1440 
showInCallScreenFragment(FragmentTransaction transaction)1441   private boolean showInCallScreenFragment(FragmentTransaction transaction) {
1442     if (didShowInCallScreen) {
1443       return false;
1444     }
1445     InCallScreen inCallScreen = InCallBindings.createInCallScreen();
1446     transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN);
1447     Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1448     didShowInCallScreen = true;
1449     return true;
1450   }
1451 
hideInCallScreenFragment(FragmentTransaction transaction)1452   private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
1453     if (!didShowInCallScreen) {
1454       return false;
1455     }
1456     InCallScreen inCallScreen = getInCallScreen();
1457     if (inCallScreen != null) {
1458       transaction.remove(inCallScreen.getInCallScreenFragment());
1459     }
1460     didShowInCallScreen = false;
1461     return true;
1462   }
1463 
showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call)1464   private boolean showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
1465     if (didShowRttCallScreen) {
1466       // This shouldn't happen since only one RTT call is allow at same time.
1467       if (!getRttCallScreen().getCallId().equals(call.getId())) {
1468         LogUtil.e("InCallActivity.showRttCallScreenFragment", "RTT call id doesn't match");
1469       }
1470       return false;
1471     }
1472     RttCallScreen rttCallScreen = RttBindings.createRttCallScreen(call.getId());
1473     transaction.add(R.id.main, rttCallScreen.getRttCallScreenFragment(), Tags.RTT_CALL_SCREEN);
1474     Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1475     didShowRttCallScreen = true;
1476     return true;
1477   }
1478 
hideRttCallScreenFragment(FragmentTransaction transaction)1479   private boolean hideRttCallScreenFragment(FragmentTransaction transaction) {
1480     if (!didShowRttCallScreen) {
1481       return false;
1482     }
1483     RttCallScreen rttCallScreen = getRttCallScreen();
1484     if (rttCallScreen != null) {
1485       transaction.remove(rttCallScreen.getRttCallScreenFragment());
1486     }
1487     didShowRttCallScreen = false;
1488     return true;
1489   }
1490 
showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call)1491   private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
1492     if (didShowVideoCallScreen) {
1493       VideoCallScreen videoCallScreen = getVideoCallScreen();
1494       if (videoCallScreen.getCallId().equals(call.getId())) {
1495         return false;
1496       }
1497       LogUtil.i(
1498           "InCallActivity.showVideoCallScreenFragment",
1499           "video call fragment exists but arguments do not match");
1500       hideVideoCallScreenFragment(transaction);
1501     }
1502 
1503     LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
1504 
1505     VideoCallScreen videoCallScreen =
1506         VideoBindings.createVideoCallScreen(
1507             call.getId(), call.getVideoTech().shouldUseSurfaceView());
1508     transaction.add(
1509         R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN);
1510 
1511     Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1512     didShowVideoCallScreen = true;
1513     return true;
1514   }
1515 
hideVideoCallScreenFragment(FragmentTransaction transaction)1516   private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
1517     if (!didShowVideoCallScreen) {
1518       return false;
1519     }
1520     VideoCallScreen videoCallScreen = getVideoCallScreen();
1521     if (videoCallScreen != null) {
1522       transaction.remove(videoCallScreen.getVideoCallScreenFragment());
1523     }
1524     didShowVideoCallScreen = false;
1525     return true;
1526   }
1527 
getAnswerScreen()1528   private AnswerScreen getAnswerScreen() {
1529     return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN);
1530   }
1531 
getInCallScreen()1532   private InCallScreen getInCallScreen() {
1533     return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN);
1534   }
1535 
getVideoCallScreen()1536   private VideoCallScreen getVideoCallScreen() {
1537     return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
1538   }
1539 
getRttCallScreen()1540   private RttCallScreen getRttCallScreen() {
1541     return (RttCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.RTT_CALL_SCREEN);
1542   }
1543 
1544   @Override
onPseudoScreenStateChanged(boolean isOn)1545   public void onPseudoScreenStateChanged(boolean isOn) {
1546     LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
1547     pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
1548   }
1549 
1550   /**
1551    * For some touch related issue, turning off the screen can be faked by drawing a black view over
1552    * the activity. All touch events started when the screen is "off" is rejected.
1553    *
1554    * @see PseudoScreenState
1555    */
1556   @Override
dispatchTouchEvent(MotionEvent event)1557   public boolean dispatchTouchEvent(MotionEvent event) {
1558     // Reject any gesture that started when the screen is in the fake off state.
1559     if (touchDownWhenPseudoScreenOff) {
1560       if (event.getAction() == MotionEvent.ACTION_UP) {
1561         touchDownWhenPseudoScreenOff = false;
1562       }
1563       return true;
1564     }
1565     // Reject all touch event when the screen is in the fake off state.
1566     if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
1567       if (event.getAction() == MotionEvent.ACTION_DOWN) {
1568         touchDownWhenPseudoScreenOff = true;
1569         LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
1570       }
1571       return true;
1572     }
1573     return super.dispatchTouchEvent(event);
1574   }
1575 
1576   @Override
newRttCallScreenDelegate(RttCallScreen videoCallScreen)1577   public RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen videoCallScreen) {
1578     return new RttCallPresenter();
1579   }
1580 
1581   private static class ShouldShowUiResult {
1582     public final boolean shouldShow;
1583     public final DialerCall call;
1584 
ShouldShowUiResult(boolean shouldShow, DialerCall call)1585     ShouldShowUiResult(boolean shouldShow, DialerCall call) {
1586       this.shouldShow = shouldShow;
1587       this.call = call;
1588     }
1589   }
1590 
1591   private static final class IntentExtraNames {
1592     static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent";
1593     static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
1594     static final String SHOW_DIALPAD = "InCallActivity.show_dialpad";
1595   }
1596 
1597   private static final class KeysForSavedInstance {
1598     static final String DIALPAD_TEXT = "InCallActivity.dialpad_text";
1599     static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
1600     static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
1601     static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
1602     static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen";
1603   }
1604 
1605   /** Request codes for pending intents. */
1606   public static final class PendingIntentRequestCodes {
1607     static final int NON_FULL_SCREEN = 0;
1608     static final int FULL_SCREEN = 1;
1609     static final int BUBBLE = 2;
1610   }
1611 
1612   private static final class Tags {
1613     static final String ANSWER_SCREEN = "tag_answer_screen";
1614     static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment";
1615     static final String IN_CALL_SCREEN = "tag_in_call_screen";
1616     static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
1617     static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
1618     static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
1619     static final String RTT_CALL_SCREEN = "tag_rtt_call_screen";
1620     static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
1621   }
1622 
1623   private static final class ConfigNames {
1624     static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
1625   }
1626 
1627   private static final class InternationalCallOnWifiCallback
1628       implements InternationalCallOnWifiDialogFragment.Callback {
1629     private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName();
1630 
1631     @Override
continueCall(@onNull String callId)1632     public void continueCall(@NonNull String callId) {
1633       LogUtil.i(TAG, "Continuing call with ID: %s", callId);
1634     }
1635 
1636     @Override
cancelCall(@onNull String callId)1637     public void cancelCall(@NonNull String callId) {
1638       DialerCall call = CallList.getInstance().getCallById(callId);
1639       if (call == null) {
1640         LogUtil.i(TAG, "Call destroyed before the dialog is closed");
1641         return;
1642       }
1643 
1644       LogUtil.i(TAG, "Disconnecting international call on WiFi");
1645       call.disconnect();
1646     }
1647   }
1648 
1649   private static final class SelectPhoneAccountListener
1650       extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener {
1651     private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName();
1652 
1653     @Override
onPhoneAccountSelected( PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId)1654     public void onPhoneAccountSelected(
1655         PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
1656       DialerCall call = CallList.getInstance().getCallById(callId);
1657       LogUtil.i(TAG, "Phone account select with call:\n%s", call);
1658 
1659       if (call != null) {
1660         call.phoneAccountSelected(selectedAccountHandle, setDefault);
1661       }
1662     }
1663 
1664     @Override
onDialogDismissed(String callId)1665     public void onDialogDismissed(String callId) {
1666       DialerCall call = CallList.getInstance().getCallById(callId);
1667       LogUtil.i(TAG, "Disconnecting call:\n%s" + call);
1668 
1669       if (call != null) {
1670         call.disconnect();
1671       }
1672     }
1673   }
1674 }
1675