blob: cc9a8e4385427d0ad3e3894aa8dfa597f46d4f3b [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
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
17package com.android.incallui;
18
19import android.content.Context;
20import android.content.Intent;
21import android.graphics.drawable.GradientDrawable;
22import android.graphics.drawable.GradientDrawable.Orientation;
23import android.os.Bundle;
24import android.support.annotation.ColorInt;
25import android.support.annotation.FloatRange;
Eric Erfanianc857f902017-05-15 14:05:33 -070026import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080027import android.support.annotation.Nullable;
28import android.support.v4.app.FragmentManager;
29import android.support.v4.app.FragmentTransaction;
30import android.support.v4.graphics.ColorUtils;
31import android.telecom.DisconnectCause;
Eric Erfanian90508232017-03-24 09:31:16 -070032import android.telephony.TelephonyManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.view.KeyEvent;
34import android.view.MenuItem;
35import android.view.MotionEvent;
36import android.view.View;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070037import com.android.dialer.common.Assert;
Eric Erfanian90508232017-03-24 09:31:16 -070038import com.android.dialer.common.ConfigProviderBindings;
Eric Erfanianccca3152017-02-22 16:32:36 -080039import com.android.dialer.common.LogUtil;
40import com.android.dialer.compat.ActivityCompat;
41import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070042import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080043import com.android.incallui.answer.bindings.AnswerBindings;
44import com.android.incallui.answer.protocol.AnswerScreen;
45import com.android.incallui.answer.protocol.AnswerScreenDelegate;
46import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
47import com.android.incallui.answerproximitysensor.PseudoScreenState;
48import com.android.incallui.call.CallList;
49import com.android.incallui.call.DialerCall;
50import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import com.android.incallui.incall.bindings.InCallBindings;
52import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
53import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
54import com.android.incallui.incall.protocol.InCallScreen;
55import com.android.incallui.incall.protocol.InCallScreenDelegate;
56import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
57import com.android.incallui.video.bindings.VideoBindings;
58import com.android.incallui.video.protocol.VideoCallScreen;
59import com.android.incallui.video.protocol.VideoCallScreenDelegate;
60import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
61
62/** Version of {@link InCallActivity} that shows the new UI */
63public 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
Eric Erfanian90508232017-03-24 09:31:16 -070078 private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
79
Eric Erfanianccca3152017-02-22 16:32:36 -080080 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
92 public InCallActivity() {
93 common = new InCallActivityCommon(this);
94 }
95
96 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -070097 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -080098 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
106 protected void onResumeFragments() {
107 super.onResumeFragments();
108 if (needDismissPendingDialogs) {
109 dismissPendingDialogs();
110 }
111 }
112
113 @Override
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
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
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
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
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
179 protected void onStop() {
180 LogUtil.i("InCallActivity.onStop", "");
181 super.onStop();
182 common.onStop();
183 isVisible = false;
184 }
185
186 @Override
187 protected void onDestroy() {
188 LogUtil.i("InCallActivity.onDestroy", "");
189 super.onDestroy();
190 common.onDestroy();
191 }
192
193 @Override
194 public void finish() {
195 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700196 // 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();
Eric Erfanianccca3152017-02-22 16:32:36 -0800212 }
213 }
214
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
244 protected void onNewIntent(Intent intent) {
245 LogUtil.i("InCallActivity.onNewIntent", "");
Eric Erfanianccca3152017-02-22 16:32:36 -0800246
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()) {
Eric Erfanian10b34a52017-05-04 08:23:17 -0700253 common.onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800254 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
255 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700256 } else {
257 common.onNewIntent(intent, false /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800258 }
259 }
260
261 @Override
262 public void onBackPressed() {
263 LogUtil.i("InCallActivity.onBackPressed", "");
264 if (!common.onBackPressed(didShowInCallScreen || didShowVideoCallScreen)) {
265 super.onBackPressed();
266 }
267 }
268
269 @Override
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
280 public boolean onKeyUp(int keyCode, KeyEvent event) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700281 return common.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800282 }
283
284 @Override
285 public boolean onKeyDown(int keyCode, KeyEvent event) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700286 return common.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800287 }
288
289 public boolean isInCallScreenAnimating() {
290 return false;
291 }
292
293 public void showConferenceFragment(boolean show) {
294 if (show) {
295 startActivity(new Intent(this, ManageConferenceActivity.class));
296 }
297 }
298
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
309 public boolean isDialpadVisible() {
310 return common.isDialpadVisible();
311 }
312
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
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
380 public boolean isVisible() {
381 return isVisible;
382 }
383
384 public boolean getCallCardFragmentVisible() {
385 return didShowInCallScreen || didShowVideoCallScreen;
386 }
387
388 public void dismissKeyguard(boolean dismiss) {
389 common.dismissKeyguard(dismiss);
390 }
391
392 public void showPostCharWaitDialog(String callId, String chars) {
393 common.showPostCharWaitDialog(callId, chars);
394 }
395
396 public void maybeShowErrorDialogOnDisconnect(DisconnectCause disconnectCause) {
397 common.maybeShowErrorDialogOnDisconnect(disconnectCause);
398 }
399
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
418 private void enableInCallOrientationEventListener(boolean enable) {
419 common.enableInCallOrientationEventListener(enable);
420 }
421
422 public void setExcludeFromRecents(boolean exclude) {
423 common.setExcludeFromRecents(exclude);
424 }
425
Eric Erfanianccca3152017-02-22 16:32:36 -0800426 @Nullable
427 public FragmentManager getDialpadFragmentManager() {
428 InCallScreen inCallScreen = getInCallScreen();
429 if (inCallScreen != null) {
430 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
431 }
432 return null;
433 }
434
435 public int getDialpadContainerId() {
436 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
437 }
438
439 @Override
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
460 public InCallScreenDelegate newInCallScreenDelegate() {
461 return new CallCardPresenter(this);
462 }
463
464 @Override
465 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
466 return new CallButtonPresenter(this);
467 }
468
469 @Override
Eric Erfanian90508232017-03-24 09:31:16 -0700470 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 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 return new VideoCallPresenter();
476 }
477
478 public void onPrimaryCallStateChanged() {
479 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "");
480 showMainInCallFragment();
481 }
482
483 public void onWiFiToLteHandover(DialerCall call) {
484 common.showWifiToLteHandoverToast(call);
485 }
486
487 public void onHandoverToWifiFailed(DialerCall call) {
488 common.showWifiFailedDialog(call);
489 }
490
Eric Erfanianc857f902017-05-15 14:05:33 -0700491 public void onInternationalCallOnWifi(@NonNull DialerCall call) {
492 LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi");
493 common.showInternationalCallOnWifiDialog(call);
494 }
495
Eric Erfanianccca3152017-02-22 16:32:36 -0800496 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
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700505 public void hideMainInCallFragment() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800506 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
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;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700530 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
531 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
Eric Erfanianccca3152017-02-22 16:32:36 -0800532 LogUtil.i(
533 "InCallActivity.showMainInCallFragment",
534 "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
535 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
536 shouldShowAnswerUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700537 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -0800538 didShowAnswerScreen,
539 didShowInCallScreen,
540 didShowVideoCallScreen);
541 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700542 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -0800543
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);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700552 } else if (shouldShowVideoUi.shouldShow) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800553 didChangeInCall = hideInCallScreenFragment(transaction);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700554 didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800555 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
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700569 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800570 DialerCall call = CallList.getInstance().getIncomingCall();
571 if (call != null) {
572 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700573 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800574 }
575
576 call = CallList.getInstance().getVideoUpgradeRequestCall();
577 if (call != null) {
578 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700579 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 }
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");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700591 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800592 }
593
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700594 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800595 }
596
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700597 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800598 DialerCall call = CallList.getInstance().getFirstCall();
599 if (call == null) {
600 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700601 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800602 }
603
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700604 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800605 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700606 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800607 }
608
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700609 if (call.hasSentVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800610 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700611 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800612 }
613
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700614 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800615 }
616
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
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700624 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
625
626 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -0800627
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())
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700632 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanianccca3152017-02-22 16:32:36 -0800633 && 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 =
Eric Erfanianfc37b022017-03-21 10:11:17 -0700644 AnswerBindings.createAnswerScreen(
645 call.getId(),
646 call.isVideoCall(),
647 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -0700648 call.getVideoTech().isSelfManagedCamera(),
649 shouldAllowAnswerAndRelease(call),
650 CallList.getInstance().getBackgroundCall() != null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800651 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
Eric Erfanian90508232017-03-24 09:31:16 -0700658 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
Eric Erfanianccca3152017-02-22 16:32:36 -0800680 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
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
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
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700721 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800722 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700723 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);
Eric Erfanianccca3152017-02-22 16:32:36 -0800731 }
732
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700733 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
734
Eric Erfanian90508232017-03-24 09:31:16 -0700735 VideoCallScreen videoCallScreen =
736 VideoBindings.createVideoCallScreen(
737 call.getId(), call.getVideoTech().shouldUseSurfaceView());
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 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
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
757 AnswerScreen getAnswerScreen() {
758 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(TAG_ANSWER_SCREEN);
759 }
760
761 InCallScreen getInCallScreen() {
762 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_IN_CALL_SCREEN);
763 }
764
765 VideoCallScreen getVideoCallScreen() {
766 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_VIDEO_CALL_SCREEN);
767 }
768
769 @Override
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
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
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700801 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -0800802 public final boolean shouldShow;
803 public final DialerCall call;
804
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700805 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800806 this.shouldShow = shouldShow;
807 this.call = call;
808 }
809 }
810}