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