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