blob: c3a68c0214f6e3780f9cdea3010292da62853858 [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;
Eric Erfanian8369df02017-05-03 10:27:13 -070043import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080044import com.android.incallui.answer.bindings.AnswerBindings;
45import com.android.incallui.answer.protocol.AnswerScreen;
46import com.android.incallui.answer.protocol.AnswerScreenDelegate;
47import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
48import com.android.incallui.answerproximitysensor.PseudoScreenState;
49import com.android.incallui.call.CallList;
50import com.android.incallui.call.DialerCall;
51import com.android.incallui.call.DialerCall.State;
Eric Erfanian2ca43182017-08-31 06:57:16 -070052import com.android.incallui.callpending.CallPendingActivity;
53import com.android.incallui.disconnectdialog.DisconnectMessage;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import com.android.incallui.incall.bindings.InCallBindings;
55import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
56import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
57import com.android.incallui.incall.protocol.InCallScreen;
58import com.android.incallui.incall.protocol.InCallScreenDelegate;
59import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
60import com.android.incallui.video.bindings.VideoBindings;
61import com.android.incallui.video.protocol.VideoCallScreen;
62import com.android.incallui.video.protocol.VideoCallScreenDelegate;
63import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
64
65/** Version of {@link InCallActivity} that shows the new UI */
66public class InCallActivity extends TransactionSafeFragmentActivity
67 implements AnswerScreenDelegateFactory,
68 InCallScreenDelegateFactory,
69 InCallButtonUiDelegateFactory,
70 VideoCallScreenDelegateFactory,
71 PseudoScreenState.StateChangedListener {
72
Eric Erfanian2ca43182017-08-31 06:57:16 -070073 public static final int PENDING_INTENT_REQUEST_CODE_NON_FULL_SCREEN = 0;
74 public static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
75 public static final int PENDING_INTENT_REQUEST_CODE_BUBBLE = 2;
76
Eric Erfanianccca3152017-02-22 16:32:36 -080077 private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen";
78 private static final String TAG_ANSWER_SCREEN = "tag_answer_screen";
79 private static final String TAG_VIDEO_CALL_SCREEN = "tag_video_call_screen";
80
81 private static final String DID_SHOW_ANSWER_SCREEN_KEY = "did_show_answer_screen";
82 private static final String DID_SHOW_IN_CALL_SCREEN_KEY = "did_show_in_call_screen";
83 private static final String DID_SHOW_VIDEO_CALL_SCREEN_KEY = "did_show_video_call_screen";
84
Eric Erfanian90508232017-03-24 09:31:16 -070085 private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
86
Eric Erfanianccca3152017-02-22 16:32:36 -080087 private final InCallActivityCommon common;
88 private boolean didShowAnswerScreen;
89 private boolean didShowInCallScreen;
90 private boolean didShowVideoCallScreen;
91 private int[] backgroundDrawableColors;
92 private GradientDrawable backgroundDrawable;
93 private boolean isVisible;
94 private View pseudoBlackScreenOverlay;
95 private boolean touchDownWhenPseudoScreenOff;
96 private boolean isInShowMainInCallFragment;
97 private boolean needDismissPendingDialogs;
wangqi9982f0d2017-10-11 17:46:07 -070098 private boolean allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -080099
100 public InCallActivity() {
101 common = new InCallActivityCommon(this);
102 }
103
104 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700105 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800106 Intent intent = new Intent(Intent.ACTION_MAIN, null);
107 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
108 intent.setClass(context, InCallActivity.class);
109 InCallActivityCommon.setIntentExtras(intent, showDialpad, newOutgoingCall, isForFullScreen);
110 return intent;
111 }
112
113 @Override
114 protected void onResumeFragments() {
115 super.onResumeFragments();
116 if (needDismissPendingDialogs) {
117 dismissPendingDialogs();
118 }
119 }
120
121 @Override
122 protected void onCreate(Bundle icicle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700123 Trace.beginSection("InCallActivity.onCreate");
Eric Erfanianccca3152017-02-22 16:32:36 -0800124 LogUtil.i("InCallActivity.onCreate", "");
125 super.onCreate(icicle);
126
Eric Erfanian2ca43182017-08-31 06:57:16 -0700127 if (getIntent().getBooleanExtra(ReturnToCallController.RETURN_TO_CALL_EXTRA_KEY, false)) {
128 Logger.get(this).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL);
129 getIntent().removeExtra(ReturnToCallController.RETURN_TO_CALL_EXTRA_KEY);
130 }
131
Eric Erfanianccca3152017-02-22 16:32:36 -0800132 if (icicle != null) {
133 didShowAnswerScreen = icicle.getBoolean(DID_SHOW_ANSWER_SCREEN_KEY);
134 didShowInCallScreen = icicle.getBoolean(DID_SHOW_IN_CALL_SCREEN_KEY);
135 didShowVideoCallScreen = icicle.getBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY);
136 }
137
138 common.onCreate(icicle);
139
140 getWindow()
141 .getDecorView()
142 .setSystemUiVisibility(
143 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
144
145 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700146 sendBroadcast(CallPendingActivity.getFinishBroadcast());
147 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 }
149
150 @Override
151 protected void onSaveInstanceState(Bundle out) {
152 LogUtil.i("InCallActivity.onSaveInstanceState", "");
153 common.onSaveInstanceState(out);
154 out.putBoolean(DID_SHOW_ANSWER_SCREEN_KEY, didShowAnswerScreen);
155 out.putBoolean(DID_SHOW_IN_CALL_SCREEN_KEY, didShowInCallScreen);
156 out.putBoolean(DID_SHOW_VIDEO_CALL_SCREEN_KEY, didShowVideoCallScreen);
157 super.onSaveInstanceState(out);
158 isVisible = false;
159 }
160
161 @Override
162 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700163 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800164 LogUtil.i("InCallActivity.onStart", "");
wangqi9982f0d2017-10-11 17:46:07 -0700165 Trace.beginSection("call super");
Eric Erfanianccca3152017-02-22 16:32:36 -0800166 super.onStart();
wangqi9982f0d2017-10-11 17:46:07 -0700167 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800168 isVisible = true;
169 showMainInCallFragment();
170 common.onStart();
171 if (ActivityCompat.isInMultiWindowMode(this)
172 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
173 // Hide the dialpad because there may not be enough room
174 showDialpadFragment(false, false);
175 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700176 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800177 }
178
179 @Override
180 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700181 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800182 LogUtil.i("InCallActivity.onResume", "");
183 super.onResume();
184 common.onResume();
185 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
186 pseudoScreenState.addListener(this);
187 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700188 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 }
190
191 /** onPause is guaranteed to be called when the InCallActivity goes in the background. */
192 @Override
193 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700194 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800195 LogUtil.i("InCallActivity.onPause", "");
196 super.onPause();
197 common.onPause();
198 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700199 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800200 }
201
202 @Override
203 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700204 Trace.beginSection("InCallActivity.onStop");
Eric Erfanianccca3152017-02-22 16:32:36 -0800205 LogUtil.i("InCallActivity.onStop", "");
wangqi4d705e52017-09-28 12:23:35 -0700206 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800207 super.onStop();
208 common.onStop();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700209 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800210 }
211
212 @Override
213 protected void onDestroy() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700214 Trace.beginSection("InCallActivity.onDestroy");
Eric Erfanianccca3152017-02-22 16:32:36 -0800215 LogUtil.i("InCallActivity.onDestroy", "");
216 super.onDestroy();
217 common.onDestroy();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700218 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800219 }
220
221 @Override
222 public void finish() {
223 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700224 // When user select incall ui from recents after the call is disconnected, it tries to launch
225 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
226 // crash.
227 // By calling finishAndRemoveTask() instead of finish() the task associated with
228 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
229 // this case.
230 //
231 // Calling finish won't clear the task and normally when an activity finishes it shouldn't
232 // clear the task since there could be parent activity in the same task that's still alive.
233 // But InCallActivity is special since it's singleInstance which means it's root activity and
234 // only instance of activity in the task. So it should be safe to also remove task when
235 // finishing.
236 // It's also necessary in the sense of it's excluded from recents. So whenever the activity
237 // finishes, the task should also be removed since it doesn't make sense to go back to it in
238 // anyway anymore.
239 super.finishAndRemoveTask();
Eric Erfanianccca3152017-02-22 16:32:36 -0800240 }
241 }
242
243 private boolean shouldCloseActivityOnFinish() {
244 if (!isVisible()) {
245 LogUtil.i(
246 "InCallActivity.shouldCloseActivityOnFinish",
247 "allowing activity to be closed because it's not visible");
248 return true;
249 }
250
twyen8efb4952017-10-06 16:35:54 -0700251 if (InCallPresenter.getInstance().isInCallUiLocked()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800252 LogUtil.i(
253 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700254 "in call ui is locked, not closing activity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800255 return false;
256 }
257
258 LogUtil.i(
259 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700260 "activity is visible and has no locks, allowing activity to close");
Eric Erfanianccca3152017-02-22 16:32:36 -0800261 return true;
262 }
263
264 @Override
265 protected void onNewIntent(Intent intent) {
266 LogUtil.i("InCallActivity.onNewIntent", "");
Eric Erfanianccca3152017-02-22 16:32:36 -0800267
268 // If the screen is off, we need to make sure it gets turned on for incoming calls.
269 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
270 // when the activity is first created. Therefore, to ensure the screen is turned on
271 // for the call waiting case, we recreate() the current activity. There should be no jank from
272 // this since the screen is already off and will remain so until our new activity is up.
273 if (!isVisible()) {
Eric Erfanian10b34a52017-05-04 08:23:17 -0700274 common.onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800275 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
276 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700277 } else {
278 common.onNewIntent(intent, false /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800279 }
280 }
281
282 @Override
283 public void onBackPressed() {
284 LogUtil.i("InCallActivity.onBackPressed", "");
285 if (!common.onBackPressed(didShowInCallScreen || didShowVideoCallScreen)) {
286 super.onBackPressed();
287 }
288 }
289
290 @Override
291 public boolean onOptionsItemSelected(MenuItem item) {
292 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
293 if (item.getItemId() == android.R.id.home) {
294 onBackPressed();
295 return true;
296 }
297 return super.onOptionsItemSelected(item);
298 }
299
300 @Override
301 public boolean onKeyUp(int keyCode, KeyEvent event) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700302 return common.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800303 }
304
305 @Override
306 public boolean onKeyDown(int keyCode, KeyEvent event) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700307 return common.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800308 }
309
310 public boolean isInCallScreenAnimating() {
311 return false;
312 }
313
314 public void showConferenceFragment(boolean show) {
315 if (show) {
316 startActivity(new Intent(this, ManageConferenceActivity.class));
317 }
318 }
319
320 public boolean showDialpadFragment(boolean show, boolean animate) {
321 boolean didChange = common.showDialpadFragment(show, animate);
322 if (didChange) {
323 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
324 // repositions itself.
325 getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
326 }
327 return didChange;
328 }
329
330 public boolean isDialpadVisible() {
331 return common.isDialpadVisible();
332 }
333
334 public void onForegroundCallChanged(DialerCall newForegroundCall) {
335 common.updateTaskDescription();
336 if (didShowAnswerScreen && newForegroundCall != null) {
337 if (newForegroundCall.getState() == State.DISCONNECTED
338 || newForegroundCall.getState() == State.IDLE) {
339 LogUtil.i(
340 "InCallActivity.onForegroundCallChanged",
341 "rejecting incoming call, not updating " + "window background color");
342 }
343 } else {
344 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
345 updateWindowBackgroundColor(0);
346 }
347 }
348
349 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
350 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
351 @ColorInt int top;
352 @ColorInt int middle;
353 @ColorInt int bottom;
354 @ColorInt int gray = 0x66000000;
355
356 if (ActivityCompat.isInMultiWindowMode(this)) {
357 top = themeColorManager.getBackgroundColorSolid();
358 middle = themeColorManager.getBackgroundColorSolid();
359 bottom = themeColorManager.getBackgroundColorSolid();
360 } else {
361 top = themeColorManager.getBackgroundColorTop();
362 middle = themeColorManager.getBackgroundColorMiddle();
363 bottom = themeColorManager.getBackgroundColorBottom();
364 }
365
366 if (progress < 0) {
367 float correctedProgress = Math.abs(progress);
368 top = ColorUtils.blendARGB(top, gray, correctedProgress);
369 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
370 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
371 }
372
373 boolean backgroundDirty = false;
374 if (backgroundDrawable == null) {
375 backgroundDrawableColors = new int[] {top, middle, bottom};
376 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
377 backgroundDirty = true;
378 } else {
379 if (backgroundDrawableColors[0] != top) {
380 backgroundDrawableColors[0] = top;
381 backgroundDirty = true;
382 }
383 if (backgroundDrawableColors[1] != middle) {
384 backgroundDrawableColors[1] = middle;
385 backgroundDirty = true;
386 }
387 if (backgroundDrawableColors[2] != bottom) {
388 backgroundDrawableColors[2] = bottom;
389 backgroundDirty = true;
390 }
391 if (backgroundDirty) {
392 backgroundDrawable.setColors(backgroundDrawableColors);
393 }
394 }
395
396 if (backgroundDirty) {
397 getWindow().setBackgroundDrawable(backgroundDrawable);
398 }
399 }
400
401 public boolean isVisible() {
402 return isVisible;
403 }
404
405 public boolean getCallCardFragmentVisible() {
406 return didShowInCallScreen || didShowVideoCallScreen;
407 }
408
409 public void dismissKeyguard(boolean dismiss) {
410 common.dismissKeyguard(dismiss);
411 }
412
413 public void showPostCharWaitDialog(String callId, String chars) {
414 common.showPostCharWaitDialog(callId, chars);
415 }
416
Eric Erfanian2ca43182017-08-31 06:57:16 -0700417 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
418 common.maybeShowErrorDialogOnDisconnect(disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 }
420
421 public void dismissPendingDialogs() {
422 if (isVisible) {
423 LogUtil.i("InCallActivity.dismissPendingDialogs", "");
424 common.dismissPendingDialogs();
425 AnswerScreen answerScreen = getAnswerScreen();
426 if (answerScreen != null) {
427 answerScreen.dismissPendingDialogs();
428 }
429 needDismissPendingDialogs = false;
430 } else {
431 // The activity is not visible and onSaveInstanceState may have been called so defer the
432 // dismissing action.
433 LogUtil.i(
434 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
435 needDismissPendingDialogs = true;
436 }
437 }
438
439 private void enableInCallOrientationEventListener(boolean enable) {
440 common.enableInCallOrientationEventListener(enable);
441 }
442
443 public void setExcludeFromRecents(boolean exclude) {
444 common.setExcludeFromRecents(exclude);
445 }
446
Eric Erfanianccca3152017-02-22 16:32:36 -0800447 @Nullable
448 public FragmentManager getDialpadFragmentManager() {
449 InCallScreen inCallScreen = getInCallScreen();
450 if (inCallScreen != null) {
451 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
452 }
453 return null;
454 }
455
456 public int getDialpadContainerId() {
457 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
458 }
459
460 @Override
461 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
462 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
463 if (call == null) {
464 // This is a work around for a bug where we attempt to create a new delegate after the call
465 // has already been removed. An example of when this can happen is:
466 // 1. incoming video call in landscape mode
467 // 2. remote party hangs up
468 // 3. activity switches from landscape to portrait
469 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
470 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
471 // because this new state is transient and the activity will be destroyed soon.
472 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
473 return new AnswerScreenPresenterStub();
474 } else {
475 return new AnswerScreenPresenter(
476 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
477 }
478 }
479
480 @Override
481 public InCallScreenDelegate newInCallScreenDelegate() {
482 return new CallCardPresenter(this);
483 }
484
485 @Override
486 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
487 return new CallButtonPresenter(this);
488 }
489
490 @Override
Eric Erfanian90508232017-03-24 09:31:16 -0700491 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
492 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
493 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
494 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
495 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800496 return new VideoCallPresenter();
497 }
498
499 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700500 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -0800501 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "");
502 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700503 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800504 }
505
506 public void onWiFiToLteHandover(DialerCall call) {
507 common.showWifiToLteHandoverToast(call);
508 }
509
510 public void onHandoverToWifiFailed(DialerCall call) {
511 common.showWifiFailedDialog(call);
512 }
513
Eric Erfanianc857f902017-05-15 14:05:33 -0700514 public void onInternationalCallOnWifi(@NonNull DialerCall call) {
515 LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi");
516 common.showInternationalCallOnWifiDialog(call);
517 }
518
Eric Erfanian938468d2017-10-24 14:05:52 -0700519 @Override
520 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
521 super.onMultiWindowModeChanged(isInMultiWindowMode);
522 if (!isInMultiWindowMode) {
523 common.updateNavigationBar(isDialpadVisible());
524 }
525 }
526
Eric Erfanianccca3152017-02-22 16:32:36 -0800527 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -0700528 if (this.allowOrientationChange == allowOrientationChange) {
529 return;
530 }
531 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -0800532 if (!allowOrientationChange) {
533 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
534 } else {
535 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
536 }
537 enableInCallOrientationEventListener(allowOrientationChange);
538 }
539
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700540 public void hideMainInCallFragment() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800541 LogUtil.i("InCallActivity.hideMainInCallFragment", "");
542 if (didShowInCallScreen || didShowVideoCallScreen) {
543 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
544 hideInCallScreenFragment(transaction);
545 hideVideoCallScreenFragment(transaction);
546 transaction.commitAllowingStateLoss();
547 getSupportFragmentManager().executePendingTransactions();
548 }
549 }
550
551 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700552 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -0800553 // If the activity's onStart method hasn't been called yet then defer doing any work.
554 if (!isVisible) {
555 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700556 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 return;
558 }
559
560 // Don't let this be reentrant.
561 if (isInShowMainInCallFragment) {
562 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700563 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800564 return;
565 }
566
567 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700568 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
569 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
Eric Erfanianccca3152017-02-22 16:32:36 -0800570 LogUtil.i(
571 "InCallActivity.showMainInCallFragment",
572 "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
573 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
574 shouldShowAnswerUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -0700575 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -0800576 didShowAnswerScreen,
577 didShowInCallScreen,
578 didShowVideoCallScreen);
579 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700580 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -0800581
582 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
583 boolean didChangeInCall;
584 boolean didChangeVideo;
585 boolean didChangeAnswer;
586 if (shouldShowAnswerUi.shouldShow) {
587 didChangeInCall = hideInCallScreenFragment(transaction);
588 didChangeVideo = hideVideoCallScreenFragment(transaction);
589 didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700590 } else if (shouldShowVideoUi.shouldShow) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800591 didChangeInCall = hideInCallScreenFragment(transaction);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700592 didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800593 didChangeAnswer = hideAnswerScreenFragment(transaction);
594 } else {
595 didChangeInCall = showInCallScreenFragment(transaction);
596 didChangeVideo = hideVideoCallScreenFragment(transaction);
597 didChangeAnswer = hideAnswerScreenFragment(transaction);
598 }
599
600 if (didChangeInCall || didChangeVideo || didChangeAnswer) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700601 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -0800602 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700603 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800604 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
605 }
606 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700607 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800608 }
609
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700610 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800611 DialerCall call = CallList.getInstance().getIncomingCall();
612 if (call != null) {
613 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700614 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800615 }
616
617 call = CallList.getInstance().getVideoUpgradeRequestCall();
618 if (call != null) {
619 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700620 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 }
622
623 // Check if we're showing the answer screen and the call is disconnected. If this condition is
624 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
625 // the user rejects an incoming call.
626 call = CallList.getInstance().getFirstCall();
627 if (call == null) {
628 call = CallList.getInstance().getBackgroundCall();
629 }
630 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
631 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700632 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800633 }
634
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700635 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800636 }
637
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700638 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800639 DialerCall call = CallList.getInstance().getFirstCall();
640 if (call == null) {
641 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700642 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800643 }
644
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700645 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800646 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700647 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800648 }
649
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700650 if (call.hasSentVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800651 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700652 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -0800653 }
654
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700655 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800656 }
657
658 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
659 // When rejecting a call the active call can become null in which case we should continue
660 // showing the answer screen.
661 if (didShowAnswerScreen && call == null) {
662 return false;
663 }
664
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700665 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
666
667 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -0800668
669 // Check if we're already showing an answer screen for this call.
670 if (didShowAnswerScreen) {
671 AnswerScreen answerScreen = getAnswerScreen();
672 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700673 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -0700674 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
675 && !answerScreen.isActionTimeout()) {
676 LogUtil.d(
677 "InCallActivity.showAnswerScreenFragment",
678 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -0800679 return false;
680 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700681 if (answerScreen.isActionTimeout()) {
682 LogUtil.i(
683 "InCallActivity.showAnswerScreenFragment",
684 "answer fragment exists but has been accepted/rejected and timed out");
685 } else {
686 LogUtil.i(
687 "InCallActivity.showAnswerScreenFragment",
688 "answer fragment exists but arguments do not match");
689 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800690 hideAnswerScreenFragment(transaction);
691 }
692
693 // Show a new answer screen.
694 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -0700695 AnswerBindings.createAnswerScreen(
696 call.getId(),
697 call.isVideoCall(),
698 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -0700699 call.getVideoTech().isSelfManagedCamera(),
700 shouldAllowAnswerAndRelease(call),
701 CallList.getInstance().getBackgroundCall() != null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800702 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), TAG_ANSWER_SCREEN);
703
704 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
705 didShowAnswerScreen = true;
706 return true;
707 }
708
Eric Erfanian90508232017-03-24 09:31:16 -0700709 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
710 if (CallList.getInstance().getActiveCall() == null) {
711 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
712 return false;
713 }
714 if (getSystemService(TelephonyManager.class).getPhoneType()
715 == TelephonyManager.PHONE_TYPE_CDMA) {
716 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
717 return false;
718 }
719 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
720 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
721 return false;
722 }
723 if (!ConfigProviderBindings.get(this).getBoolean(CONFIG_ANSWER_AND_RELEASE_ENABLED, true)) {
724 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
725 return false;
726 }
727
728 return true;
729 }
730
Eric Erfanianccca3152017-02-22 16:32:36 -0800731 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
732 if (!didShowAnswerScreen) {
733 return false;
734 }
735 AnswerScreen answerScreen = getAnswerScreen();
736 if (answerScreen != null) {
737 transaction.remove(answerScreen.getAnswerScreenFragment());
738 }
739
740 didShowAnswerScreen = false;
741 return true;
742 }
743
744 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
745 if (didShowInCallScreen) {
746 return false;
747 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700748 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
749 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), TAG_IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
751 didShowInCallScreen = true;
752 return true;
753 }
754
755 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
756 if (!didShowInCallScreen) {
757 return false;
758 }
759 InCallScreen inCallScreen = getInCallScreen();
760 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700761 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 }
763 didShowInCallScreen = false;
764 return true;
765 }
766
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700767 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800768 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700769 VideoCallScreen videoCallScreen = getVideoCallScreen();
770 if (videoCallScreen.getCallId().equals(call.getId())) {
771 return false;
772 }
773 LogUtil.i(
774 "InCallActivity.showVideoCallScreenFragment",
775 "video call fragment exists but arguments do not match");
776 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -0800777 }
778
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700779 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
780
Eric Erfanian90508232017-03-24 09:31:16 -0700781 VideoCallScreen videoCallScreen =
782 VideoBindings.createVideoCallScreen(
783 call.getId(), call.getVideoTech().shouldUseSurfaceView());
Eric Erfanianccca3152017-02-22 16:32:36 -0800784 transaction.add(R.id.main, videoCallScreen.getVideoCallScreenFragment(), TAG_VIDEO_CALL_SCREEN);
785
786 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
787 didShowVideoCallScreen = true;
788 return true;
789 }
790
791 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
792 if (!didShowVideoCallScreen) {
793 return false;
794 }
795 VideoCallScreen videoCallScreen = getVideoCallScreen();
796 if (videoCallScreen != null) {
797 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
798 }
799 didShowVideoCallScreen = false;
800 return true;
801 }
802
803 AnswerScreen getAnswerScreen() {
804 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(TAG_ANSWER_SCREEN);
805 }
806
807 InCallScreen getInCallScreen() {
808 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_IN_CALL_SCREEN);
809 }
810
811 VideoCallScreen getVideoCallScreen() {
812 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(TAG_VIDEO_CALL_SCREEN);
813 }
814
815 @Override
816 public void onPseudoScreenStateChanged(boolean isOn) {
817 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
818 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
819 }
820
821 /**
822 * For some touch related issue, turning off the screen can be faked by drawing a black view over
823 * the activity. All touch events started when the screen is "off" is rejected.
824 *
825 * @see PseudoScreenState
826 */
827 @Override
828 public boolean dispatchTouchEvent(MotionEvent event) {
829 // Reject any gesture that started when the screen is in the fake off state.
830 if (touchDownWhenPseudoScreenOff) {
831 if (event.getAction() == MotionEvent.ACTION_UP) {
832 touchDownWhenPseudoScreenOff = false;
833 }
834 return true;
835 }
836 // Reject all touch event when the screen is in the fake off state.
837 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
838 if (event.getAction() == MotionEvent.ACTION_DOWN) {
839 touchDownWhenPseudoScreenOff = true;
840 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
841 }
842 return true;
843 }
844 return super.dispatchTouchEvent(event);
845 }
846
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700847 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -0800848 public final boolean shouldShow;
849 public final DialerCall call;
850
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700851 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800852 this.shouldShow = shouldShow;
853 this.call = call;
854 }
855 }
856}