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.content.Context;
20 import android.content.Intent;
21 import android.graphics.drawable.GradientDrawable;
22 import android.graphics.drawable.GradientDrawable.Orientation;
23 import android.os.Bundle;
24 import android.support.annotation.ColorInt;
25 import android.support.annotation.FloatRange;
26 import android.support.annotation.NonNull;
27 import android.support.annotation.Nullable;
28 import android.support.v4.app.FragmentManager;
29 import android.support.v4.app.FragmentTransaction;
30 import android.support.v4.graphics.ColorUtils;
31 import android.telecom.DisconnectCause;
32 import android.telephony.TelephonyManager;
33 import android.view.KeyEvent;
34 import android.view.MenuItem;
35 import android.view.MotionEvent;
36 import android.view.View;
37 import com.android.dialer.common.Assert;
38 import com.android.dialer.common.ConfigProviderBindings;
39 import com.android.dialer.common.LogUtil;
40 import com.android.dialer.compat.ActivityCompat;
41 import com.android.dialer.logging.Logger;
42 import com.android.dialer.logging.ScreenEvent;
43 import com.android.incallui.answer.bindings.AnswerBindings;
44 import com.android.incallui.answer.protocol.AnswerScreen;
45 import com.android.incallui.answer.protocol.AnswerScreenDelegate;
46 import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
47 import com.android.incallui.answerproximitysensor.PseudoScreenState;
48 import com.android.incallui.call.CallList;
49 import com.android.incallui.call.DialerCall;
50 import com.android.incallui.call.DialerCall.State;
51 import com.android.incallui.incall.bindings.InCallBindings;
52 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
53 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
54 import com.android.incallui.incall.protocol.InCallScreen;
55 import com.android.incallui.incall.protocol.InCallScreenDelegate;
56 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
57 import com.android.incallui.video.bindings.VideoBindings;
58 import com.android.incallui.video.protocol.VideoCallScreen;
59 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
60 import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
61 
62 /** Version of {@link InCallActivity} that shows the new UI */
63 public class InCallActivity extends TransactionSafeFragmentActivity
64     implements AnswerScreenDelegateFactory,
65         InCallScreenDelegateFactory,
66         InCallButtonUiDelegateFactory,
67         VideoCallScreenDelegateFactory,
68         PseudoScreenState.StateChangedListener {
69 
70   private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen";
71   private static final String TAG_ANSWER_SCREEN = "tag_answer_screen";
72   private static final String TAG_VIDEO_CALL_SCREEN = "tag_video_call_screen";
73 
74   private static final String DID_SHOW_ANSWER_SCREEN_KEY = "did_show_answer_screen";
75   private static final String DID_SHOW_IN_CALL_SCREEN_KEY = "did_show_in_call_screen";
76   private static final String DID_SHOW_VIDEO_CALL_SCREEN_KEY = "did_show_video_call_screen";
77 
78   private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
79 
80   private final InCallActivityCommon common;
81   private boolean didShowAnswerScreen;
82   private boolean didShowInCallScreen;
83   private boolean didShowVideoCallScreen;
84   private int[] backgroundDrawableColors;
85   private GradientDrawable backgroundDrawable;
86   private boolean isVisible;
87   private View pseudoBlackScreenOverlay;
88   private boolean touchDownWhenPseudoScreenOff;
89   private boolean isInShowMainInCallFragment;
90   private boolean needDismissPendingDialogs;
91 
InCallActivity()92   public InCallActivity() {
93     common = new InCallActivityCommon(this);
94   }
95 
getIntent( Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen)96   public static Intent getIntent(
97       Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
98     Intent intent = new Intent(Intent.ACTION_MAIN, null);
99     intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
100     intent.setClass(context, InCallActivity.class);
101     InCallActivityCommon.setIntentExtras(intent, showDialpad, newOutgoingCall, isForFullScreen);
102     return intent;
103   }
104 
105   @Override
onResumeFragments()106   protected void onResumeFragments() {
107     super.onResumeFragments();
108     if (needDismissPendingDialogs) {
109       dismissPendingDialogs();
110     }
111   }
112 
113   @Override
onCreate(Bundle icicle)114   protected void onCreate(Bundle icicle) {
115     LogUtil.i("InCallActivity.onCreate", "");
116     super.onCreate(icicle);
117 
118     if (icicle != null) {
119       didShowAnswerScreen = icicle.getBoolean(DID_SHOW_ANSWER_SCREEN_KEY);
120       didShowInCallScreen = icicle.getBoolean(DID_SHOW_IN_CALL_SCREEN_KEY);
121       didShowVideoCallScreen = icicle.getBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY);
122     }
123 
124     common.onCreate(icicle);
125 
126     getWindow()
127         .getDecorView()
128         .setSystemUiVisibility(
129             View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
130 
131     pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
132   }
133 
134   @Override
onSaveInstanceState(Bundle out)135   protected void onSaveInstanceState(Bundle out) {
136     LogUtil.i("InCallActivity.onSaveInstanceState", "");
137     common.onSaveInstanceState(out);
138     out.putBoolean(DID_SHOW_ANSWER_SCREEN_KEY, didShowAnswerScreen);
139     out.putBoolean(DID_SHOW_IN_CALL_SCREEN_KEY, didShowInCallScreen);
140     out.putBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY, didShowVideoCallScreen);
141     super.onSaveInstanceState(out);
142     isVisible = false;
143   }
144 
145   @Override
onStart()146   protected void onStart() {
147     LogUtil.i("InCallActivity.onStart", "");
148     super.onStart();
149     isVisible = true;
150     showMainInCallFragment();
151     common.onStart();
152     if (ActivityCompat.isInMultiWindowMode(this)
153         && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
154       // Hide the dialpad because there may not be enough room
155       showDialpadFragment(false, false);
156     }
157   }
158 
159   @Override
onResume()160   protected void onResume() {
161     LogUtil.i("InCallActivity.onResume", "");
162     super.onResume();
163     common.onResume();
164     PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
165     pseudoScreenState.addListener(this);
166     onPseudoScreenStateChanged(pseudoScreenState.isOn());
167   }
168 
169   /** onPause is guaranteed to be called when the InCallActivity goes in the background. */
170   @Override
onPause()171   protected void onPause() {
172     LogUtil.i("InCallActivity.onPause", "");
173     super.onPause();
174     common.onPause();
175     InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
176   }
177 
178   @Override
onStop()179   protected void onStop() {
180     LogUtil.i("InCallActivity.onStop", "");
181     super.onStop();
182     common.onStop();
183     isVisible = false;
184   }
185 
186   @Override
onDestroy()187   protected void onDestroy() {
188     LogUtil.i("InCallActivity.onDestroy", "");
189     super.onDestroy();
190     common.onDestroy();
191   }
192 
193   @Override
finish()194   public void finish() {
195     if (shouldCloseActivityOnFinish()) {
196       // When user select incall ui from recents after the call is disconnected, it tries to launch
197       // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
198       // crash.
199       // By calling finishAndRemoveTask() instead of finish() the task associated with
200       // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
201       // this case.
202       //
203       // Calling finish won't clear the task and normally when an activity finishes it shouldn't
204       // clear the task since there could be parent activity in the same task that's still alive.
205       // But InCallActivity is special since it's singleInstance which means it's root activity and
206       // only instance of activity in the task. So it should be safe to also remove task when
207       // finishing.
208       // It's also necessary in the sense of it's excluded from recents. So whenever the activity
209       // finishes, the task should also be removed since it doesn't make sense to go back to it in
210       // anyway anymore.
211       super.finishAndRemoveTask();
212     }
213   }
214 
shouldCloseActivityOnFinish()215   private boolean shouldCloseActivityOnFinish() {
216     if (!isVisible()) {
217       LogUtil.i(
218           "InCallActivity.shouldCloseActivityOnFinish",
219           "allowing activity to be closed because it's not visible");
220       return true;
221     }
222 
223     if (common.hasPendingDialogs()) {
224       LogUtil.i(
225           "InCallActivity.shouldCloseActivityOnFinish", "dialog is visible, not closing activity");
226       return false;
227     }
228 
229     AnswerScreen answerScreen = getAnswerScreen();
230     if (answerScreen != null && answerScreen.hasPendingDialogs()) {
231       LogUtil.i(
232           "InCallActivity.shouldCloseActivityOnFinish",
233           "answer screen dialog is visible, not closing activity");
234       return false;
235     }
236 
237     LogUtil.i(
238         "InCallActivity.shouldCloseActivityOnFinish",
239         "activity is visible and has no dialogs, allowing activity to close");
240     return true;
241   }
242 
243   @Override
onNewIntent(Intent intent)244   protected void onNewIntent(Intent intent) {
245     LogUtil.i("InCallActivity.onNewIntent", "");
246 
247     // If the screen is off, we need to make sure it gets turned on for incoming calls.
248     // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
249     // when the activity is first created. Therefore, to ensure the screen is turned on
250     // for the call waiting case, we recreate() the current activity. There should be no jank from
251     // this since the screen is already off and will remain so until our new activity is up.
252     if (!isVisible()) {
253       common.onNewIntent(intent, true /* isRecreating */);
254       LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
255       recreate();
256     } else {
257       common.onNewIntent(intent, false /* isRecreating */);
258     }
259   }
260 
261   @Override
onBackPressed()262   public void onBackPressed() {
263     LogUtil.i("InCallActivity.onBackPressed", "");
264     if (!common.onBackPressed(didShowInCallScreen || didShowVideoCallScreen)) {
265       super.onBackPressed();
266     }
267   }
268 
269   @Override
onOptionsItemSelected(MenuItem item)270   public boolean onOptionsItemSelected(MenuItem item) {
271     LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
272     if (item.getItemId() == android.R.id.home) {
273       onBackPressed();
274       return true;
275     }
276     return super.onOptionsItemSelected(item);
277   }
278 
279   @Override
onKeyUp(int keyCode, KeyEvent event)280   public boolean onKeyUp(int keyCode, KeyEvent event) {
281     return common.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
282   }
283 
284   @Override
onKeyDown(int keyCode, KeyEvent event)285   public boolean onKeyDown(int keyCode, KeyEvent event) {
286     return common.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
287   }
288 
isInCallScreenAnimating()289   public boolean isInCallScreenAnimating() {
290     return false;
291   }
292 
showConferenceFragment(boolean show)293   public void showConferenceFragment(boolean show) {
294     if (show) {
295       startActivity(new Intent(this, ManageConferenceActivity.class));
296     }
297   }
298 
showDialpadFragment(boolean show, boolean animate)299   public boolean showDialpadFragment(boolean show, boolean animate) {
300     boolean didChange = common.showDialpadFragment(show, animate);
301     if (didChange) {
302       // Note:  onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
303       // repositions itself.
304       getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
305     }
306     return didChange;
307   }
308 
isDialpadVisible()309   public boolean isDialpadVisible() {
310     return common.isDialpadVisible();
311   }
312 
onForegroundCallChanged(DialerCall newForegroundCall)313   public void onForegroundCallChanged(DialerCall newForegroundCall) {
314     common.updateTaskDescription();
315     if (didShowAnswerScreen && newForegroundCall != null) {
316       if (newForegroundCall.getState() == State.DISCONNECTED
317           || newForegroundCall.getState() == State.IDLE) {
318         LogUtil.i(
319             "InCallActivity.onForegroundCallChanged",
320             "rejecting incoming call, not updating " + "window background color");
321       }
322     } else {
323       LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
324       updateWindowBackgroundColor(0);
325     }
326   }
327 
updateWindowBackgroundColor(@loatRangefrom = -1f, to = 1.0f) float progress)328   public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
329     ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
330     @ColorInt int top;
331     @ColorInt int middle;
332     @ColorInt int bottom;
333     @ColorInt int gray = 0x66000000;
334 
335     if (ActivityCompat.isInMultiWindowMode(this)) {
336       top = themeColorManager.getBackgroundColorSolid();
337       middle = themeColorManager.getBackgroundColorSolid();
338       bottom = themeColorManager.getBackgroundColorSolid();
339     } else {
340       top = themeColorManager.getBackgroundColorTop();
341       middle = themeColorManager.getBackgroundColorMiddle();
342       bottom = themeColorManager.getBackgroundColorBottom();
343     }
344 
345     if (progress < 0) {
346       float correctedProgress = Math.abs(progress);
347       top = ColorUtils.blendARGB(top, gray, correctedProgress);
348       middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
349       bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
350     }
351 
352     boolean backgroundDirty = false;
353     if (backgroundDrawable == null) {
354       backgroundDrawableColors = new int[] {top, middle, bottom};
355       backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
356       backgroundDirty = true;
357     } else {
358       if (backgroundDrawableColors[0] != top) {
359         backgroundDrawableColors[0] = top;
360         backgroundDirty = true;
361       }
362       if (backgroundDrawableColors[1] != middle) {
363         backgroundDrawableColors[1] = middle;
364         backgroundDirty = true;
365       }
366       if (backgroundDrawableColors[2] != bottom) {
367         backgroundDrawableColors[2] = bottom;
368         backgroundDirty = true;
369       }
370       if (backgroundDirty) {
371         backgroundDrawable.setColors(backgroundDrawableColors);
372       }
373     }
374 
375     if (backgroundDirty) {
376       getWindow().setBackgroundDrawable(backgroundDrawable);
377     }
378   }
379 
isVisible()380   public boolean isVisible() {
381     return isVisible;
382   }
383 
getCallCardFragmentVisible()384   public boolean getCallCardFragmentVisible() {
385     return didShowInCallScreen || didShowVideoCallScreen;
386   }
387 
dismissKeyguard(boolean dismiss)388   public void dismissKeyguard(boolean dismiss) {
389     common.dismissKeyguard(dismiss);
390   }
391 
showPostCharWaitDialog(String callId, String chars)392   public void showPostCharWaitDialog(String callId, String chars) {
393     common.showPostCharWaitDialog(callId, chars);
394   }
395 
maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause)396   public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
397     common.maybeShowErrorDialogOnDisconnect(disconnectCause);
398   }
399 
dismissPendingDialogs()400   public void dismissPendingDialogs() {
401     if (isVisible) {
402       LogUtil.i("InCallActivity.dismissPendingDialogs", "");
403       common.dismissPendingDialogs();
404       AnswerScreen answerScreen = getAnswerScreen();
405       if (answerScreen != null) {
406         answerScreen.dismissPendingDialogs();
407       }
408       needDismissPendingDialogs = false;
409     } else {
410       // The activity is not visible and onSaveInstanceState may have been called so defer the
411       // dismissing action.
412       LogUtil.i(
413           "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
414       needDismissPendingDialogs = true;
415     }
416   }
417 
enableInCallOrientationEventListener(boolean enable)418   private void enableInCallOrientationEventListener(boolean enable) {
419     common.enableInCallOrientationEventListener(enable);
420   }
421 
setExcludeFromRecents(boolean exclude)422   public void setExcludeFromRecents(boolean exclude) {
423     common.setExcludeFromRecents(exclude);
424   }
425 
426   @Nullable
getDialpadFragmentManager()427   public FragmentManager getDialpadFragmentManager() {
428     InCallScreen inCallScreen = getInCallScreen();
429     if (inCallScreen != null) {
430       return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
431     }
432     return null;
433   }
434 
getDialpadContainerId()435   public int getDialpadContainerId() {
436     return getInCallScreen().getAnswerAndDialpadContainerResourceId();
437   }
438 
439   @Override
newAnswerScreenDelegate(AnswerScreen answerScreen)440   public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
441     DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
442     if (call == null) {
443       // This is a work around for a bug where we attempt to create a new delegate after the call
444       // has already been removed. An example of when this can happen is:
445       // 1. incoming video call in landscape mode
446       // 2. remote party hangs up
447       // 3. activity switches from landscape to portrait
448       // At step #3 the answer fragment will try to create a new answer delegate but the call won't
449       // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
450       // because this new state is transient and the activity will be destroyed soon.
451       LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
452       return new AnswerScreenPresenterStub();
453     } else {
454       return new AnswerScreenPresenter(
455           this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
456     }
457   }
458 
459   @Override
newInCallScreenDelegate()460   public InCallScreenDelegate newInCallScreenDelegate() {
461     return new CallCardPresenter(this);
462   }
463 
464   @Override
newInCallButtonUiDelegate()465   public InCallButtonUiDelegate newInCallButtonUiDelegate() {
466     return new CallButtonPresenter(this);
467   }
468 
469   @Override
newVideoCallScreenDelegate(VideoCallScreen videoCallScreen)470   public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
471     DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
472     if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
473       return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
474     }
475     return new VideoCallPresenter();
476   }
477 
onPrimaryCallStateChanged()478   public void onPrimaryCallStateChanged() {
479     LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "");
480     showMainInCallFragment();
481   }
482 
onWiFiToLteHandover(DialerCall call)483   public void onWiFiToLteHandover(DialerCall call) {
484     common.showWifiToLteHandoverToast(call);
485   }
486 
onHandoverToWifiFailed(DialerCall call)487   public void onHandoverToWifiFailed(DialerCall call) {
488     common.showWifiFailedDialog(call);
489   }
490 
onInternationalCallOnWifi(@onNull DialerCall call)491   public void onInternationalCallOnWifi(@NonNull DialerCall call) {
492     LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi");
493     common.showInternationalCallOnWifiDialog(call);
494   }
495 
setAllowOrientationChange(boolean allowOrientationChange)496   public void setAllowOrientationChange(boolean allowOrientationChange) {
497     if (!allowOrientationChange) {
498       setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
499     } else {
500       setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
501     }
502     enableInCallOrientationEventListener(allowOrientationChange);
503   }
504 
hideMainInCallFragment()505   public void hideMainInCallFragment() {
506     LogUtil.i("InCallActivity.hideMainInCallFragment", "");
507     if (didShowInCallScreen || didShowVideoCallScreen) {
508       FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
509       hideInCallScreenFragment(transaction);
510       hideVideoCallScreenFragment(transaction);
511       transaction.commitAllowingStateLoss();
512       getSupportFragmentManager().executePendingTransactions();
513     }
514   }
515 
showMainInCallFragment()516   private void showMainInCallFragment() {
517     // If the activity's onStart method hasn't been called yet then defer doing any work.
518     if (!isVisible) {
519       LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
520       return;
521     }
522 
523     // Don't let this be reentrant.
524     if (isInShowMainInCallFragment) {
525       LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
526       return;
527     }
528 
529     isInShowMainInCallFragment = true;
530     ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
531     ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
532     LogUtil.i(
533         "InCallActivity.showMainInCallFragment",
534         "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
535             + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
536         shouldShowAnswerUi.shouldShow,
537         shouldShowVideoUi.shouldShow,
538         didShowAnswerScreen,
539         didShowInCallScreen,
540         didShowVideoCallScreen);
541     // Only video call ui allows orientation change.
542     setAllowOrientationChange(shouldShowVideoUi.shouldShow);
543 
544     FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
545     boolean didChangeInCall;
546     boolean didChangeVideo;
547     boolean didChangeAnswer;
548     if (shouldShowAnswerUi.shouldShow) {
549       didChangeInCall = hideInCallScreenFragment(transaction);
550       didChangeVideo = hideVideoCallScreenFragment(transaction);
551       didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
552     } else if (shouldShowVideoUi.shouldShow) {
553       didChangeInCall = hideInCallScreenFragment(transaction);
554       didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
555       didChangeAnswer = hideAnswerScreenFragment(transaction);
556     } else {
557       didChangeInCall = showInCallScreenFragment(transaction);
558       didChangeVideo = hideVideoCallScreenFragment(transaction);
559       didChangeAnswer = hideAnswerScreenFragment(transaction);
560     }
561 
562     if (didChangeInCall || didChangeVideo || didChangeAnswer) {
563       transaction.commitNow();
564       Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
565     }
566     isInShowMainInCallFragment = false;
567   }
568 
getShouldShowAnswerUi()569   private ShouldShowUiResult getShouldShowAnswerUi() {
570     DialerCall call = CallList.getInstance().getIncomingCall();
571     if (call != null) {
572       LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
573       return new ShouldShowUiResult(true, call);
574     }
575 
576     call = CallList.getInstance().getVideoUpgradeRequestCall();
577     if (call != null) {
578       LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
579       return new ShouldShowUiResult(true, call);
580     }
581 
582     // Check if we're showing the answer screen and the call is disconnected. If this condition is
583     // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
584     // the user rejects an incoming call.
585     call = CallList.getInstance().getFirstCall();
586     if (call == null) {
587       call = CallList.getInstance().getBackgroundCall();
588     }
589     if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
590       LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
591       return new ShouldShowUiResult(true, call);
592     }
593 
594     return new ShouldShowUiResult(false, null);
595   }
596 
getShouldShowVideoUi()597   private static ShouldShowUiResult getShouldShowVideoUi() {
598     DialerCall call = CallList.getInstance().getFirstCall();
599     if (call == null) {
600       LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
601       return new ShouldShowUiResult(false, null);
602     }
603 
604     if (call.isVideoCall()) {
605       LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
606       return new ShouldShowUiResult(true, call);
607     }
608 
609     if (call.hasSentVideoUpgradeRequest()) {
610       LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
611       return new ShouldShowUiResult(true, call);
612     }
613 
614     return new ShouldShowUiResult(false, null);
615   }
616 
showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call)617   private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
618     // When rejecting a call the active call can become null in which case we should continue
619     // showing the answer screen.
620     if (didShowAnswerScreen && call == null) {
621       return false;
622     }
623 
624     Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
625 
626     boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
627 
628     // Check if we're already showing an answer screen for this call.
629     if (didShowAnswerScreen) {
630       AnswerScreen answerScreen = getAnswerScreen();
631       if (answerScreen.getCallId().equals(call.getId())
632           && answerScreen.isVideoCall() == call.isVideoCall()
633           && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest) {
634         return false;
635       }
636       LogUtil.i(
637           "InCallActivity.showAnswerScreenFragment",
638           "answer fragment exists but arguments do not match");
639       hideAnswerScreenFragment(transaction);
640     }
641 
642     // Show a new answer screen.
643     AnswerScreen answerScreen =
644         AnswerBindings.createAnswerScreen(
645             call.getId(),
646             call.isVideoCall(),
647             isVideoUpgradeRequest,
648             call.getVideoTech().isSelfManagedCamera(),
649             shouldAllowAnswerAndRelease(call),
650             CallList.getInstance().getBackgroundCall() != null);
651     transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), TAG_ANSWER_SCREEN);
652 
653     Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
654     didShowAnswerScreen = true;
655     return true;
656   }
657 
shouldAllowAnswerAndRelease(DialerCall call)658   private boolean shouldAllowAnswerAndRelease(DialerCall call) {
659     if (CallList.getInstance().getActiveCall() == null) {
660       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
661       return false;
662     }
663     if (getSystemService(TelephonyManager.class).getPhoneType()
664         == TelephonyManager.PHONE_TYPE_CDMA) {
665       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
666       return false;
667     }
668     if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
669       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
670       return false;
671     }
672     if (!ConfigProviderBindings.get(this).getBoolean(CONFIG_ANSWER_AND_RELEASE_ENABLED, true)) {
673       LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
674       return false;
675     }
676 
677     return true;
678   }
679 
hideAnswerScreenFragment(FragmentTransaction transaction)680   private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
681     if (!didShowAnswerScreen) {
682       return false;
683     }
684     AnswerScreen answerScreen = getAnswerScreen();
685     if (answerScreen != null) {
686       transaction.remove(answerScreen.getAnswerScreenFragment());
687     }
688 
689     didShowAnswerScreen = false;
690     return true;
691   }
692 
showInCallScreenFragment(FragmentTransaction transaction)693   private boolean showInCallScreenFragment(FragmentTransaction transaction) {
694     if (didShowInCallScreen) {
695       return false;
696     }
697     InCallScreen inCallScreen = getInCallScreen();
698     if (inCallScreen == null) {
699       inCallScreen = InCallBindings.createInCallScreen();
700       transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), TAG_IN_CALL_SCREEN);
701     } else {
702       transaction.show(inCallScreen.getInCallScreenFragment());
703     }
704     Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
705     didShowInCallScreen = true;
706     return true;
707   }
708 
hideInCallScreenFragment(FragmentTransaction transaction)709   private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
710     if (!didShowInCallScreen) {
711       return false;
712     }
713     InCallScreen inCallScreen = getInCallScreen();
714     if (inCallScreen != null) {
715       transaction.hide(inCallScreen.getInCallScreenFragment());
716     }
717     didShowInCallScreen = false;
718     return true;
719   }
720 
showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call)721   private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
722     if (didShowVideoCallScreen) {
723       VideoCallScreen videoCallScreen = getVideoCallScreen();
724       if (videoCallScreen.getCallId().equals(call.getId())) {
725         return false;
726       }
727       LogUtil.i(
728           "InCallActivity.showVideoCallScreenFragment",
729           "video call fragment exists but arguments do not match");
730       hideVideoCallScreenFragment(transaction);
731     }
732 
733     LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
734 
735     VideoCallScreen videoCallScreen =
736         VideoBindings.createVideoCallScreen(
737             call.getId(), call.getVideoTech().shouldUseSurfaceView());
738     transaction.add(R.id.main, videoCallScreen.getVideoCallScreenFragment(), TAG_VIDEO_CALL_SCREEN);
739 
740     Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
741     didShowVideoCallScreen = true;
742     return true;
743   }
744 
hideVideoCallScreenFragment(FragmentTransaction transaction)745   private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
746     if (!didShowVideoCallScreen) {
747       return false;
748     }
749     VideoCallScreen videoCallScreen = getVideoCallScreen();
750     if (videoCallScreen != null) {
751       transaction.remove(videoCallScreen.getVideoCallScreenFragment());
752     }
753     didShowVideoCallScreen = false;
754     return true;
755   }
756 
getAnswerScreen()757   AnswerScreen getAnswerScreen() {
758     return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(TAG_ANSWER_SCREEN);
759   }
760 
getInCallScreen()761   InCallScreen getInCallScreen() {
762     return (InCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_IN_CALL_SCREEN);
763   }
764 
getVideoCallScreen()765   VideoCallScreen getVideoCallScreen() {
766     return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_VIDEO_CALL_SCREEN);
767   }
768 
769   @Override
onPseudoScreenStateChanged(boolean isOn)770   public void onPseudoScreenStateChanged(boolean isOn) {
771     LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
772     pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
773   }
774 
775   /**
776    * For some touch related issue, turning off the screen can be faked by drawing a black view over
777    * the activity. All touch events started when the screen is "off" is rejected.
778    *
779    * @see PseudoScreenState
780    */
781   @Override
dispatchTouchEvent(MotionEvent event)782   public boolean dispatchTouchEvent(MotionEvent event) {
783     // Reject any gesture that started when the screen is in the fake off state.
784     if (touchDownWhenPseudoScreenOff) {
785       if (event.getAction() == MotionEvent.ACTION_UP) {
786         touchDownWhenPseudoScreenOff = false;
787       }
788       return true;
789     }
790     // Reject all touch event when the screen is in the fake off state.
791     if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
792       if (event.getAction() == MotionEvent.ACTION_DOWN) {
793         touchDownWhenPseudoScreenOff = true;
794         LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
795       }
796       return true;
797     }
798     return super.dispatchTouchEvent(event);
799   }
800 
801   private static class ShouldShowUiResult {
802     public final boolean shouldShow;
803     public final DialerCall call;
804 
ShouldShowUiResult(boolean shouldShow, DialerCall call)805     ShouldShowUiResult(boolean shouldShow, DialerCall call) {
806       this.shouldShow = shouldShow;
807       this.call = call;
808     }
809   }
810 }
811