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