blob: c509e48902ce8a3326294e57b80aca7ce7ed423c [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;
Eric Erfanian2ca43182017-08-31 06:57:16 -070024import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.support.annotation.ColorInt;
26import android.support.annotation.FloatRange;
Eric Erfanianc857f902017-05-15 14:05:33 -070027import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080028import android.support.annotation.Nullable;
29import android.support.v4.app.FragmentManager;
30import android.support.v4.app.FragmentTransaction;
31import android.support.v4.graphics.ColorUtils;
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 Erfanianccca3152017-02-22 16:32:36 -080038import com.android.dialer.common.LogUtil;
39import com.android.dialer.compat.ActivityCompat;
Eric Erfanian2ca43182017-08-31 06:57:16 -070040import com.android.dialer.configprovider.ConfigProviderBindings;
41import com.android.dialer.logging.DialerImpression;
Eric Erfanianccca3152017-02-22 16:32:36 -080042import com.android.dialer.logging.Logger;
weijiaxu94df7202017-10-25 18:21:41 -070043import com.android.dialer.logging.LoggingBindings;
44import com.android.dialer.logging.LoggingBindingsFactory;
Eric Erfanian8369df02017-05-03 10:27:13 -070045import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080046import com.android.incallui.answer.bindings.AnswerBindings;
47import com.android.incallui.answer.protocol.AnswerScreen;
48import com.android.incallui.answer.protocol.AnswerScreenDelegate;
49import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
50import com.android.incallui.answerproximitysensor.PseudoScreenState;
51import com.android.incallui.call.CallList;
52import com.android.incallui.call.DialerCall;
53import com.android.incallui.call.DialerCall.State;
Eric Erfanian2ca43182017-08-31 06:57:16 -070054import com.android.incallui.callpending.CallPendingActivity;
55import com.android.incallui.disconnectdialog.DisconnectMessage;
Eric Erfanianccca3152017-02-22 16:32:36 -080056import com.android.incallui.incall.bindings.InCallBindings;
57import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
58import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
59import com.android.incallui.incall.protocol.InCallScreen;
60import com.android.incallui.incall.protocol.InCallScreenDelegate;
61import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
62import com.android.incallui.video.bindings.VideoBindings;
63import com.android.incallui.video.protocol.VideoCallScreen;
64import com.android.incallui.video.protocol.VideoCallScreenDelegate;
65import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
66
67/** Version of {@link InCallActivity} that shows the new UI */
68public class InCallActivity extends TransactionSafeFragmentActivity
69 implements AnswerScreenDelegateFactory,
70 InCallScreenDelegateFactory,
71 InCallButtonUiDelegateFactory,
72 VideoCallScreenDelegateFactory,
73 PseudoScreenState.StateChangedListener {
74
Eric Erfanian2ca43182017-08-31 06:57:16 -070075 public static final int PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN = 0;
76 public static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
77 public static final int PENDING_INTENT_REQUEST_CODE_BUBBLE = 2;
78
Eric Erfanianccca3152017-02-22 16:32:36 -080079 private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen";
80 private static final String TAG_ANSWER_SCREEN = "tag_answer_screen";
81 private static final String TAG_VIDEO_CALL_SCREEN = "tag_video_call_screen";
82
83 private static final String DID_SHOW_ANSWER_SCREEN_KEY = "did_show_answer_screen";
84 private static final String DID_SHOW_IN_CALL_SCREEN_KEY = "did_show_in_call_screen";
85 private static final String DID_SHOW_VIDEO_CALL_SCREEN_KEY = "did_show_video_call_screen";
86
Eric Erfanian90508232017-03-24 09:31:16 -070087 private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
88
Eric Erfanianccca3152017-02-22 16:32:36 -080089 private final InCallActivityCommon common;
90 private boolean didShowAnswerScreen;
91 private boolean didShowInCallScreen;
92 private boolean didShowVideoCallScreen;
93 private int[] backgroundDrawableColors;
94 private GradientDrawable backgroundDrawable;
95 private boolean isVisible;
96 private View pseudoBlackScreenOverlay;
97 private boolean touchDownWhenPseudoScreenOff;
98 private boolean isInShowMainInCallFragment;
99 private boolean needDismissPendingDialogs;
wangqi9982f0d2017-10-11 17:46:07 -0700100 private boolean allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -0800101
102 public InCallActivity() {
103 common = new InCallActivityCommon(this);
104 }
105
106 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700107 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 Intent intent = new Intent(Intent.ACTION_MAIN, null);
109 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
110 intent.setClass(context, InCallActivity.class);
111 InCallActivityCommon.setIntentExtras(intent, showDialpad, newOutgoingCall, isForFullScreen);
112 return intent;
113 }
114
115 @Override
116 protected void onResumeFragments() {
117 super.onResumeFragments();
118 if (needDismissPendingDialogs) {
119 dismissPendingDialogs();
120 }
121 }
122
123 @Override
124 protected void onCreate(Bundle icicle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700125 Trace.beginSection("InCallActivity.onCreate");
Eric Erfanianccca3152017-02-22 16:32:36 -0800126 LogUtil.i("InCallActivity.onCreate", "");
127 super.onCreate(icicle);
128
Eric Erfanian2ca43182017-08-31 06:57:16 -0700129 if (getIntent().getBooleanExtra(ReturnToCallController.RETURN_TO_CALL_EXTRA_KEY, false)) {
130 Logger.get(this).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL);
131 getIntent().removeExtra(ReturnToCallController.RETURN_TO_CALL_EXTRA_KEY);
132 }
133
Eric Erfanianccca3152017-02-22 16:32:36 -0800134 if (icicle != null) {
135 didShowAnswerScreen = icicle.getBoolean(DID_SHOW_ANSWER_SCREEN_KEY);
136 didShowInCallScreen = icicle.getBoolean(DID_SHOW_IN_CALL_SCREEN_KEY);
137 didShowVideoCallScreen = icicle.getBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY);
138 }
139
140 common.onCreate(icicle);
141
142 getWindow()
143 .getDecorView()
144 .setSystemUiVisibility(
145 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
146
147 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700148 sendBroadcast(CallPendingActivity.getFinishBroadcast());
149 Trace.endSection();
weijiaxu94df7202017-10-25 18:21:41 -0700150 if (getApplicationContext() instanceof LoggingBindingsFactory) {
151 LoggingBindings loggingBindings =
152 ((LoggingBindingsFactory) getApplicationContext()).newLoggingBindings();
153 loggingBindings.logStopLatencyTimer(
154 LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
155 loggingBindings.logStopLatencyTimer(
156 LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
157 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800158 }
159
160 @Override
161 protected void onSaveInstanceState(Bundle out) {
162 LogUtil.i("InCallActivity.onSaveInstanceState", "");
163 common.onSaveInstanceState(out);
164 out.putBoolean(DID_SHOW_ANSWER_SCREEN_KEY, didShowAnswerScreen);
165 out.putBoolean(DID_SHOW_IN_CALL_SCREEN_KEY, didShowInCallScreen);
166 out.putBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY, didShowVideoCallScreen);
167 super.onSaveInstanceState(out);
168 isVisible = false;
169 }
170
171 @Override
172 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700173 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800174 LogUtil.i("InCallActivity.onStart", "");
wangqi9982f0d2017-10-11 17:46:07 -0700175 Trace.beginSection("call super");
Eric Erfanianccca3152017-02-22 16:32:36 -0800176 super.onStart();
wangqi9982f0d2017-10-11 17:46:07 -0700177 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800178 isVisible = true;
179 showMainInCallFragment();
180 common.onStart();
181 if (ActivityCompat.isInMultiWindowMode(this)
182 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
183 // Hide the dialpad because there may not be enough room
184 showDialpadFragment(false, false);
185 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700186 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 }
188
189 @Override
190 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700191 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800192 LogUtil.i("InCallActivity.onResume", "");
193 super.onResume();
194 common.onResume();
195 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
196 pseudoScreenState.addListener(this);
197 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700198 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800199 }
200
201 /** onPause is guaranteed to be called when the InCallActivity goes in the background. */
202 @Override
203 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700204 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800205 LogUtil.i("InCallActivity.onPause", "");
206 super.onPause();
207 common.onPause();
208 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700209 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800210 }
211
212 @Override
213 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700214 Trace.beginSection("InCallActivity.onStop");
Eric Erfanianccca3152017-02-22 16:32:36 -0800215 LogUtil.i("InCallActivity.onStop", "");
wangqi4d705e52017-09-28 12:23:35 -0700216 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800217 super.onStop();
218 common.onStop();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700219 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800220 }
221
222 @Override
223 protected void onDestroy() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700224 Trace.beginSection("InCallActivity.onDestroy");
Eric Erfanianccca3152017-02-22 16:32:36 -0800225 LogUtil.i("InCallActivity.onDestroy", "");
226 super.onDestroy();
227 common.onDestroy();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700228 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 }
230
231 @Override
232 public void finish() {
233 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700234 // When user select incall ui from recents after the call is disconnected, it tries to launch
235 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
236 // crash.
237 // By calling finishAndRemoveTask() instead of finish() the task associated with
238 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
239 // this case.
240 //
241 // Calling finish won't clear the task and normally when an activity finishes it shouldn't
242 // clear the task since there could be parent activity in the same task that's still alive.
243 // But InCallActivity is special since it's singleInstance which means it's root activity and
244 // only instance of activity in the task. So it should be safe to also remove task when
245 // finishing.
246 // It's also necessary in the sense of it's excluded from recents. So whenever the activity
247 // finishes, the task should also be removed since it doesn't make sense to go back to it in
248 // anyway anymore.
249 super.finishAndRemoveTask();
Eric Erfanianccca3152017-02-22 16:32:36 -0800250 }
251 }
252
253 private boolean shouldCloseActivityOnFinish() {
254 if (!isVisible()) {
255 LogUtil.i(
256 "InCallActivity.shouldCloseActivityOnFinish",
257 "allowing activity to be closed because it's not visible");
258 return true;
259 }
260
twyen8efb4952017-10-06 16:35:54 -0700261 if (InCallPresenter.getInstance().isInCallUiLocked()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800262 LogUtil.i(
263 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700264 "in call ui is locked, not closing activity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800265 return false;
266 }
267
268 LogUtil.i(
269 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700270 "activity is visible and has no locks, allowing activity to close");
Eric Erfanianccca3152017-02-22 16:32:36 -0800271 return true;
272 }
273
274 @Override
275 protected void onNewIntent(Intent intent) {
276 LogUtil.i("InCallActivity.onNewIntent", "");
Eric Erfanianccca3152017-02-22 16:32:36 -0800277
278 // If the screen is off, we need to make sure it gets turned on for incoming calls.
279 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
280 // when the activity is first created. Therefore, to ensure the screen is turned on
281 // for the call waiting case, we recreate() the current activity. There should be no jank from
282 // this since the screen is already off and will remain so until our new activity is up.
283 if (!isVisible()) {
Eric Erfanian10b34a52017-05-04 08:23:17 -0700284 common.onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800285 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
286 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700287 } else {
288 common.onNewIntent(intent, false /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800289 }
290 }
291
292 @Override
293 public void onBackPressed() {
294 LogUtil.i("InCallActivity.onBackPressed", "");
295 if (!common.onBackPressed(didShowInCallScreen || didShowVideoCallScreen)) {
296 super.onBackPressed();
297 }
298 }
299
300 @Override
301 public boolean onOptionsItemSelected(MenuItem item) {
302 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
303 if (item.getItemId() == android.R.id.home) {
304 onBackPressed();
305 return true;
306 }
307 return super.onOptionsItemSelected(item);
308 }
309
310 @Override
311 public boolean onKeyUp(int keyCode, KeyEvent event) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700312 return common.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800313 }
314
315 @Override
316 public boolean onKeyDown(int keyCode, KeyEvent event) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700317 return common.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800318 }
319
320 public boolean isInCallScreenAnimating() {
321 return false;
322 }
323
324 public void showConferenceFragment(boolean show) {
325 if (show) {
326 startActivity(new Intent(this, ManageConferenceActivity.class));
327 }
328 }
329
330 public boolean showDialpadFragment(boolean show, boolean animate) {
331 boolean didChange = common.showDialpadFragment(show, animate);
332 if (didChange) {
333 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
334 // repositions itself.
335 getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
336 }
337 return didChange;
338 }
339
340 public boolean isDialpadVisible() {
341 return common.isDialpadVisible();
342 }
343
344 public void onForegroundCallChanged(DialerCall newForegroundCall) {
345 common.updateTaskDescription();
346 if (didShowAnswerScreen && newForegroundCall != null) {
347 if (newForegroundCall.getState() == State.DISCONNECTED
348 || newForegroundCall.getState() == State.IDLE) {
349 LogUtil.i(
350 "InCallActivity.onForegroundCallChanged",
351 "rejecting incoming call, not updating " + "window background color");
352 }
353 } else {
354 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
355 updateWindowBackgroundColor(0);
356 }
357 }
358
359 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
360 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
361 @ColorInt int top;
362 @ColorInt int middle;
363 @ColorInt int bottom;
364 @ColorInt int gray = 0x66000000;
365
366 if (ActivityCompat.isInMultiWindowMode(this)) {
367 top = themeColorManager.getBackgroundColorSolid();
368 middle = themeColorManager.getBackgroundColorSolid();
369 bottom = themeColorManager.getBackgroundColorSolid();
370 } else {
371 top = themeColorManager.getBackgroundColorTop();
372 middle = themeColorManager.getBackgroundColorMiddle();
373 bottom = themeColorManager.getBackgroundColorBottom();
374 }
375
376 if (progress < 0) {
377 float correctedProgress = Math.abs(progress);
378 top = ColorUtils.blendARGB(top, gray, correctedProgress);
379 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
380 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
381 }
382
383 boolean backgroundDirty = false;
384 if (backgroundDrawable == null) {
385 backgroundDrawableColors = new int[] {top, middle, bottom};
386 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
387 backgroundDirty = true;
388 } else {
389 if (backgroundDrawableColors[0] != top) {
390 backgroundDrawableColors[0] = top;
391 backgroundDirty = true;
392 }
393 if (backgroundDrawableColors[1] != middle) {
394 backgroundDrawableColors[1] = middle;
395 backgroundDirty = true;
396 }
397 if (backgroundDrawableColors[2] != bottom) {
398 backgroundDrawableColors[2] = bottom;
399 backgroundDirty = true;
400 }
401 if (backgroundDirty) {
402 backgroundDrawable.setColors(backgroundDrawableColors);
403 }
404 }
405
406 if (backgroundDirty) {
407 getWindow().setBackgroundDrawable(backgroundDrawable);
408 }
409 }
410
411 public boolean isVisible() {
412 return isVisible;
413 }
414
415 public boolean getCallCardFragmentVisible() {
416 return didShowInCallScreen || didShowVideoCallScreen;
417 }
418
419 public void dismissKeyguard(boolean dismiss) {
420 common.dismissKeyguard(dismiss);
421 }
422
423 public void showPostCharWaitDialog(String callId, String chars) {
424 common.showPostCharWaitDialog(callId, chars);
425 }
426
Eric Erfanian2ca43182017-08-31 06:57:16 -0700427 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
428 common.maybeShowErrorDialogOnDisconnect(disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800429 }
430
431 public void dismissPendingDialogs() {
432 if (isVisible) {
433 LogUtil.i("InCallActivity.dismissPendingDialogs", "");
434 common.dismissPendingDialogs();
435 AnswerScreen answerScreen = getAnswerScreen();
436 if (answerScreen != null) {
437 answerScreen.dismissPendingDialogs();
438 }
439 needDismissPendingDialogs = false;
440 } else {
441 // The activity is not visible and onSaveInstanceState may have been called so defer the
442 // dismissing action.
443 LogUtil.i(
444 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
445 needDismissPendingDialogs = true;
446 }
447 }
448
449 private void enableInCallOrientationEventListener(boolean enable) {
450 common.enableInCallOrientationEventListener(enable);
451 }
452
453 public void setExcludeFromRecents(boolean exclude) {
454 common.setExcludeFromRecents(exclude);
455 }
456
Eric Erfanianccca3152017-02-22 16:32:36 -0800457 @Nullable
458 public FragmentManager getDialpadFragmentManager() {
459 InCallScreen inCallScreen = getInCallScreen();
460 if (inCallScreen != null) {
461 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
462 }
463 return null;
464 }
465
466 public int getDialpadContainerId() {
467 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
468 }
469
470 @Override
471 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
472 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
473 if (call == null) {
474 // This is a work around for a bug where we attempt to create a new delegate after the call
475 // has already been removed. An example of when this can happen is:
476 // 1. incoming video call in landscape mode
477 // 2. remote party hangs up
478 // 3. activity switches from landscape to portrait
479 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
480 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
481 // because this new state is transient and the activity will be destroyed soon.
482 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
483 return new AnswerScreenPresenterStub();
484 } else {
485 return new AnswerScreenPresenter(
486 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
487 }
488 }
489
490 @Override
491 public InCallScreenDelegate newInCallScreenDelegate() {
492 return new CallCardPresenter(this);
493 }
494
495 @Override
496 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
497 return new CallButtonPresenter(this);
498 }
499
500 @Override
Eric Erfanian90508232017-03-24 09:31:16 -0700501 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
502 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
503 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
504 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
505 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800506 return new VideoCallPresenter();
507 }
508
509 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700510 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -0800511 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "");
512 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700513 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800514 }
515
516 public void onWiFiToLteHandover(DialerCall call) {
517 common.showWifiToLteHandoverToast(call);
518 }
519
520 public void onHandoverToWifiFailed(DialerCall call) {
521 common.showWifiFailedDialog(call);
522 }
523
Eric Erfanianc857f902017-05-15 14:05:33 -0700524 public void onInternationalCallOnWifi(@NonNull DialerCall call) {
525 LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi");
526 common.showInternationalCallOnWifiDialog(call);
527 }
528
Eric Erfanian938468d2017-10-24 14:05:52 -0700529 @Override
530 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
531 super.onMultiWindowModeChanged(isInMultiWindowMode);
532 if (!isInMultiWindowMode) {
533 common.updateNavigationBar(isDialpadVisible());
534 }
535 }
536
Eric Erfanianccca3152017-02-22 16:32:36 -0800537 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -0700538 if (this.allowOrientationChange == allowOrientationChange) {
539 return;
540 }
541 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 if (!allowOrientationChange) {
543 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
544 } else {
545 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
546 }
547 enableInCallOrientationEventListener(allowOrientationChange);
548 }
549
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700550 public void hideMainInCallFragment() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 LogUtil.i("InCallActivity.hideMainInCallFragment", "");
552 if (didShowInCallScreen || didShowVideoCallScreen) {
553 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
554 hideInCallScreenFragment(transaction);
555 hideVideoCallScreenFragment(transaction);
556 transaction.commitAllowingStateLoss();
557 getSupportFragmentManager().executePendingTransactions();
558 }
559 }
560
561 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700562 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 // If the activity's onStart method hasn't been called yet then defer doing any work.
564 if (!isVisible) {
565 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700566 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800567 return;
568 }
569
570 // Don't let this be reentrant.
571 if (isInShowMainInCallFragment) {
572 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700573 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800574 return;
575 }
576
577 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700578 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
579 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
Eric Erfanianccca3152017-02-22 16:32:36 -0800580 LogUtil.i(
581 "InCallActivity.showMainInCallFragment",
582 "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
583 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
584 shouldShowAnswerUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700585 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -0800586 didShowAnswerScreen,
587 didShowInCallScreen,
588 didShowVideoCallScreen);
589 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700590 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -0800591
592 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
593 boolean didChangeInCall;
594 boolean didChangeVideo;
595 boolean didChangeAnswer;
596 if (shouldShowAnswerUi.shouldShow) {
597 didChangeInCall = hideInCallScreenFragment(transaction);
598 didChangeVideo = hideVideoCallScreenFragment(transaction);
599 didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700600 } else if (shouldShowVideoUi.shouldShow) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800601 didChangeInCall = hideInCallScreenFragment(transaction);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700602 didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800603 didChangeAnswer = hideAnswerScreenFragment(transaction);
604 } else {
605 didChangeInCall = showInCallScreenFragment(transaction);
606 didChangeVideo = hideVideoCallScreenFragment(transaction);
607 didChangeAnswer = hideAnswerScreenFragment(transaction);
608 }
609
610 if (didChangeInCall || didChangeVideo || didChangeAnswer) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700611 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -0800612 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700613 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800614 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
615 }
616 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700617 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800618 }
619
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700620 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 DialerCall call = CallList.getInstance().getIncomingCall();
622 if (call != null) {
623 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700624 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800625 }
626
627 call = CallList.getInstance().getVideoUpgradeRequestCall();
628 if (call != null) {
629 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700630 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800631 }
632
633 // Check if we're showing the answer screen and the call is disconnected. If this condition is
634 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
635 // the user rejects an incoming call.
636 call = CallList.getInstance().getFirstCall();
637 if (call == null) {
638 call = CallList.getInstance().getBackgroundCall();
639 }
640 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
641 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700642 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800643 }
644
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700645 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800646 }
647
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700648 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800649 DialerCall call = CallList.getInstance().getFirstCall();
650 if (call == null) {
651 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700652 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800653 }
654
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700655 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800656 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700657 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800658 }
659
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700660 if (call.hasSentVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800661 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700662 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800663 }
664
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700665 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800666 }
667
668 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
669 // When rejecting a call the active call can become null in which case we should continue
670 // showing the answer screen.
671 if (didShowAnswerScreen && call == null) {
672 return false;
673 }
674
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700675 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
676
677 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -0800678
679 // Check if we're already showing an answer screen for this call.
680 if (didShowAnswerScreen) {
681 AnswerScreen answerScreen = getAnswerScreen();
682 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700683 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -0700684 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
685 && !answerScreen.isActionTimeout()) {
686 LogUtil.d(
687 "InCallActivity.showAnswerScreenFragment",
688 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 return false;
690 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700691 if (answerScreen.isActionTimeout()) {
692 LogUtil.i(
693 "InCallActivity.showAnswerScreenFragment",
694 "answer fragment exists but has been accepted/rejected and timed out");
695 } else {
696 LogUtil.i(
697 "InCallActivity.showAnswerScreenFragment",
698 "answer fragment exists but arguments do not match");
699 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800700 hideAnswerScreenFragment(transaction);
701 }
702
703 // Show a new answer screen.
704 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -0700705 AnswerBindings.createAnswerScreen(
706 call.getId(),
707 call.isVideoCall(),
708 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -0700709 call.getVideoTech().isSelfManagedCamera(),
710 shouldAllowAnswerAndRelease(call),
711 CallList.getInstance().getBackgroundCall() != null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), TAG_ANSWER_SCREEN);
713
714 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
715 didShowAnswerScreen = true;
716 return true;
717 }
718
Eric Erfanian90508232017-03-24 09:31:16 -0700719 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
720 if (CallList.getInstance().getActiveCall() == null) {
721 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
722 return false;
723 }
724 if (getSystemService(TelephonyManager.class).getPhoneType()
725 == TelephonyManager.PHONE_TYPE_CDMA) {
726 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
727 return false;
728 }
729 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
730 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
731 return false;
732 }
733 if (!ConfigProviderBindings.get(this).getBoolean(CONFIG_ANSWER_AND_RELEASE_ENABLED, true)) {
734 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
735 return false;
736 }
737
738 return true;
739 }
740
Eric Erfanianccca3152017-02-22 16:32:36 -0800741 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
742 if (!didShowAnswerScreen) {
743 return false;
744 }
745 AnswerScreen answerScreen = getAnswerScreen();
746 if (answerScreen != null) {
747 transaction.remove(answerScreen.getAnswerScreenFragment());
748 }
749
750 didShowAnswerScreen = false;
751 return true;
752 }
753
754 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
755 if (didShowInCallScreen) {
756 return false;
757 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700758 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
759 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), TAG_IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800760 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
761 didShowInCallScreen = true;
762 return true;
763 }
764
765 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
766 if (!didShowInCallScreen) {
767 return false;
768 }
769 InCallScreen inCallScreen = getInCallScreen();
770 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700771 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -0800772 }
773 didShowInCallScreen = false;
774 return true;
775 }
776
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700777 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800778 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700779 VideoCallScreen videoCallScreen = getVideoCallScreen();
780 if (videoCallScreen.getCallId().equals(call.getId())) {
781 return false;
782 }
783 LogUtil.i(
784 "InCallActivity.showVideoCallScreenFragment",
785 "video call fragment exists but arguments do not match");
786 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -0800787 }
788
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700789 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
790
Eric Erfanian90508232017-03-24 09:31:16 -0700791 VideoCallScreen videoCallScreen =
792 VideoBindings.createVideoCallScreen(
793 call.getId(), call.getVideoTech().shouldUseSurfaceView());
Eric Erfanianccca3152017-02-22 16:32:36 -0800794 transaction.add(R.id.main, videoCallScreen.getVideoCallScreenFragment(), TAG_VIDEO_CALL_SCREEN);
795
796 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
797 didShowVideoCallScreen = true;
798 return true;
799 }
800
801 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
802 if (!didShowVideoCallScreen) {
803 return false;
804 }
805 VideoCallScreen videoCallScreen = getVideoCallScreen();
806 if (videoCallScreen != null) {
807 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
808 }
809 didShowVideoCallScreen = false;
810 return true;
811 }
812
813 AnswerScreen getAnswerScreen() {
814 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(TAG_ANSWER_SCREEN);
815 }
816
817 InCallScreen getInCallScreen() {
818 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_IN_CALL_SCREEN);
819 }
820
821 VideoCallScreen getVideoCallScreen() {
822 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_VIDEO_CALL_SCREEN);
823 }
824
825 @Override
826 public void onPseudoScreenStateChanged(boolean isOn) {
827 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
828 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
829 }
830
831 /**
832 * For some touch related issue, turning off the screen can be faked by drawing a black view over
833 * the activity. All touch events started when the screen is "off" is rejected.
834 *
835 * @see PseudoScreenState
836 */
837 @Override
838 public boolean dispatchTouchEvent(MotionEvent event) {
839 // Reject any gesture that started when the screen is in the fake off state.
840 if (touchDownWhenPseudoScreenOff) {
841 if (event.getAction() == MotionEvent.ACTION_UP) {
842 touchDownWhenPseudoScreenOff = false;
843 }
844 return true;
845 }
846 // Reject all touch event when the screen is in the fake off state.
847 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
848 if (event.getAction() == MotionEvent.ACTION_DOWN) {
849 touchDownWhenPseudoScreenOff = true;
850 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
851 }
852 return true;
853 }
854 return super.dispatchTouchEvent(event);
855 }
856
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700857 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 public final boolean shouldShow;
859 public final DialerCall call;
860
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700861 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 this.shouldShow = shouldShow;
863 this.call = call;
864 }
865 }
866}