blob: f842aedf7985e35cf132bcd3a56bd914cc8d794b [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
linyuhc3968e62017-11-20 17:40:50 -080019import android.app.ActivityManager;
20import android.app.ActivityManager.AppTask;
linyuh57b093b2017-11-17 14:32:32 -080021import android.app.ActivityManager.TaskDescription;
linyuh7b86f562017-11-16 11:24:09 -080022import android.app.AlertDialog;
linyuhf99f6302017-11-15 11:23:51 -080023import android.app.Dialog;
linyuh57b093b2017-11-17 14:32:32 -080024import android.app.KeyguardManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.content.Context;
26import android.content.Intent;
linyuhc3968e62017-11-20 17:40:50 -080027import android.content.res.Configuration;
Eric Erfanianccca3152017-02-22 16:32:36 -080028import android.graphics.drawable.GradientDrawable;
29import android.graphics.drawable.GradientDrawable.Orientation;
30import android.os.Bundle;
Eric Erfanian2ca43182017-08-31 06:57:16 -070031import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080032import android.support.annotation.ColorInt;
33import android.support.annotation.FloatRange;
linyuhc3968e62017-11-20 17:40:50 -080034import android.support.annotation.IntDef;
Eric Erfanianc857f902017-05-15 14:05:33 -070035import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080036import android.support.annotation.Nullable;
linyuhc3968e62017-11-20 17:40:50 -080037import android.support.annotation.VisibleForTesting;
Eric Erfanianccca3152017-02-22 16:32:36 -080038import android.support.v4.app.FragmentManager;
39import android.support.v4.app.FragmentTransaction;
linyuh57b093b2017-11-17 14:32:32 -080040import android.support.v4.content.res.ResourcesCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.support.v4.graphics.ColorUtils;
linyuhc3968e62017-11-20 17:40:50 -080042import android.telecom.CallAudioState;
43import android.telecom.PhoneAccountHandle;
Eric Erfanian90508232017-03-24 09:31:16 -070044import android.telephony.TelephonyManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080045import android.view.KeyEvent;
46import android.view.MenuItem;
47import android.view.MotionEvent;
48import android.view.View;
linyuh9c327da2017-11-14 12:33:48 -080049import android.view.WindowManager;
linyuhc3968e62017-11-20 17:40:50 -080050import android.view.animation.Animation;
51import android.view.animation.AnimationUtils;
linyuh7b86f562017-11-16 11:24:09 -080052import android.widget.CheckBox;
53import android.widget.Toast;
linyuhf99f6302017-11-15 11:23:51 -080054import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
linyuhc3968e62017-11-20 17:40:50 -080055import com.android.dialer.animation.AnimUtils;
56import com.android.dialer.animation.AnimationListenerAdapter;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070057import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080058import com.android.dialer.common.LogUtil;
weijiaxu650e7cc2017-10-31 12:38:54 -070059import com.android.dialer.common.concurrent.ThreadUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080060import com.android.dialer.compat.ActivityCompat;
linyuhc3968e62017-11-20 17:40:50 -080061import com.android.dialer.compat.CompatUtils;
Eric Erfanian2ca43182017-08-31 06:57:16 -070062import com.android.dialer.configprovider.ConfigProviderBindings;
Eric Erfanianccca3152017-02-22 16:32:36 -080063import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070064import com.android.dialer.logging.ScreenEvent;
zachh7a96dc72018-02-20 22:16:03 -080065import com.android.dialer.metrics.Metrics;
66import com.android.dialer.metrics.MetricsComponent;
linyuhc3968e62017-11-20 17:40:50 -080067import com.android.dialer.util.ViewUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.incallui.answer.bindings.AnswerBindings;
69import com.android.incallui.answer.protocol.AnswerScreen;
70import com.android.incallui.answer.protocol.AnswerScreenDelegate;
71import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
72import com.android.incallui.answerproximitysensor.PseudoScreenState;
linyuh57b093b2017-11-17 14:32:32 -080073import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import com.android.incallui.call.CallList;
75import com.android.incallui.call.DialerCall;
76import com.android.incallui.call.DialerCall.State;
linyuh57b093b2017-11-17 14:32:32 -080077import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070078import com.android.incallui.callpending.CallPendingActivity;
79import com.android.incallui.disconnectdialog.DisconnectMessage;
Eric Erfanianccca3152017-02-22 16:32:36 -080080import com.android.incallui.incall.bindings.InCallBindings;
81import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
82import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
83import com.android.incallui.incall.protocol.InCallScreen;
84import com.android.incallui.incall.protocol.InCallScreenDelegate;
85import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
linyuh7b86f562017-11-16 11:24:09 -080086import com.android.incallui.incalluilock.InCallUiLock;
wangqi219b8702018-02-13 09:34:41 -080087import com.android.incallui.rtt.bindings.RttBindings;
88import com.android.incallui.rtt.protocol.RttCallScreen;
89import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
90import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
linyuhf99f6302017-11-15 11:23:51 -080091import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080092import com.android.incallui.video.bindings.VideoBindings;
93import com.android.incallui.video.protocol.VideoCallScreen;
94import com.android.incallui.video.protocol.VideoCallScreenDelegate;
95import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
linyuhc3968e62017-11-20 17:40:50 -080096import com.google.common.base.Optional;
97import java.lang.annotation.Retention;
98import java.lang.annotation.RetentionPolicy;
99import java.util.ArrayList;
100import java.util.List;
Eric Erfanianccca3152017-02-22 16:32:36 -0800101
102/** Version of {@link InCallActivity} that shows the new UI */
103public class InCallActivity extends TransactionSafeFragmentActivity
104 implements AnswerScreenDelegateFactory,
105 InCallScreenDelegateFactory,
106 InCallButtonUiDelegateFactory,
107 VideoCallScreenDelegateFactory,
wangqi219b8702018-02-13 09:34:41 -0800108 RttCallScreenDelegateFactory,
Eric Erfanianccca3152017-02-22 16:32:36 -0800109 PseudoScreenState.StateChangedListener {
110
linyuhc3968e62017-11-20 17:40:50 -0800111 @Retention(RetentionPolicy.SOURCE)
112 @IntDef({
113 DIALPAD_REQUEST_NONE,
114 DIALPAD_REQUEST_SHOW,
115 DIALPAD_REQUEST_HIDE,
116 })
117 @interface DialpadRequestType {}
Eric Erfanian2ca43182017-08-31 06:57:16 -0700118
linyuhc3968e62017-11-20 17:40:50 -0800119 private static final int DIALPAD_REQUEST_NONE = 1;
120 private static final int DIALPAD_REQUEST_SHOW = 2;
121 private static final int DIALPAD_REQUEST_HIDE = 3;
linyuh57b093b2017-11-17 14:32:32 -0800122
linyuhc3968e62017-11-20 17:40:50 -0800123 private static Optional<Integer> audioRouteForTesting = Optional.absent();
linyuh57b093b2017-11-17 14:32:32 -0800124
linyuhc3968e62017-11-20 17:40:50 -0800125 private final InternationalCallOnWifiCallback internationalCallOnWifiCallback =
126 new InternationalCallOnWifiCallback();
127 private final SelectPhoneAccountListener selectPhoneAccountListener =
128 new SelectPhoneAccountListener();
Eric Erfanianccca3152017-02-22 16:32:36 -0800129
linyuhc3968e62017-11-20 17:40:50 -0800130 private Animation dialpadSlideInAnimation;
131 private Animation dialpadSlideOutAnimation;
132 private Dialog errorDialog;
133 private GradientDrawable backgroundDrawable;
linyuh69a25062017-11-15 16:18:51 -0800134 private InCallOrientationEventListener inCallOrientationEventListener;
linyuhc3968e62017-11-20 17:40:50 -0800135 private View pseudoBlackScreenOverlay;
136 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
137 private String dtmfTextToPrepopulate;
138 private String showPostCharWaitDialogCallId;
139 private String showPostCharWaitDialogChars;
140 private boolean allowOrientationChange;
141 private boolean animateDialpadOnShow;
Eric Erfanianccca3152017-02-22 16:32:36 -0800142 private boolean didShowAnswerScreen;
143 private boolean didShowInCallScreen;
144 private boolean didShowVideoCallScreen;
wangqi219b8702018-02-13 09:34:41 -0800145 private boolean didShowRttCallScreen;
linyuh9c327da2017-11-14 12:33:48 -0800146 private boolean dismissKeyguard;
Eric Erfanianccca3152017-02-22 16:32:36 -0800147 private boolean isInShowMainInCallFragment;
linyuhc3968e62017-11-20 17:40:50 -0800148 private boolean isRecreating; // whether the activity is going to be recreated
149 private boolean isVisible;
Eric Erfanianccca3152017-02-22 16:32:36 -0800150 private boolean needDismissPendingDialogs;
linyuhc3968e62017-11-20 17:40:50 -0800151 private boolean showPostCharWaitDialogOnResume;
152 private boolean touchDownWhenPseudoScreenOff;
153 private int[] backgroundDrawableColors;
154 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800155
156 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700157 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800158 Intent intent = new Intent(Intent.ACTION_MAIN, null);
159 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
160 intent.setClass(context, InCallActivity.class);
linyuhc3968e62017-11-20 17:40:50 -0800161 if (showDialpad) {
162 intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true);
163 }
164 intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall);
165 intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800166 return intent;
167 }
168
169 @Override
170 protected void onResumeFragments() {
171 super.onResumeFragments();
172 if (needDismissPendingDialogs) {
173 dismissPendingDialogs();
174 }
175 }
176
177 @Override
linyuhc3968e62017-11-20 17:40:50 -0800178 protected void onCreate(Bundle bundle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700179 Trace.beginSection("InCallActivity.onCreate");
linyuhc3968e62017-11-20 17:40:50 -0800180 super.onCreate(bundle);
Eric Erfanianccca3152017-02-22 16:32:36 -0800181
linyuhc3968e62017-11-20 17:40:50 -0800182 if (bundle != null) {
183 didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
184 didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
185 didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
wangqi153af2f2018-02-15 16:21:49 -0800186 didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 }
188
linyuhc3968e62017-11-20 17:40:50 -0800189 setWindowFlags();
190 setContentView(R.layout.incall_screen);
191 internalResolveIntent(getIntent());
192
193 boolean isLandscape =
194 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
195 boolean isRtl = ViewUtil.isRtl();
196 if (isLandscape) {
197 dialpadSlideInAnimation =
198 AnimationUtils.loadAnimation(
199 this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
200 dialpadSlideOutAnimation =
201 AnimationUtils.loadAnimation(
202 this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
203 } else {
204 dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
205 dialpadSlideOutAnimation =
206 AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
207 }
208 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
209 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
210 dialpadSlideOutAnimation.setAnimationListener(
211 new AnimationListenerAdapter() {
212 @Override
213 public void onAnimationEnd(Animation animation) {
214 hideDialpadFragment();
215 }
216 });
217
218 if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
219 // If the dialpad was shown before, set related variables so that it can be shown and
220 // populated with the previous DTMF text during onResume().
221 if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) {
222 boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD);
223 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
224 animateDialpadOnShow = false;
225 }
226 dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT);
227
228 SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
229 (SelectPhoneAccountDialogFragment)
230 getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT);
231 if (selectPhoneAccountDialogFragment != null) {
232 selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener);
233 }
234 }
235
236 InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment =
237 (InternationalCallOnWifiDialogFragment)
238 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
239 if (existingInternationalCallOnWifiDialogFragment != null) {
240 existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback);
241 }
242
linyuh69a25062017-11-15 16:18:51 -0800243 inCallOrientationEventListener = new InCallOrientationEventListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800244
245 getWindow()
246 .getDecorView()
247 .setSystemUiVisibility(
248 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
249
250 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700251 sendBroadcast(CallPendingActivity.getFinishBroadcast());
252 Trace.endSection();
zachh7a96dc72018-02-20 22:16:03 -0800253 MetricsComponent.get(this)
254 .metrics()
255 .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
256 MetricsComponent.get(this)
257 .metrics()
258 .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800259 }
260
linyuhc3968e62017-11-20 17:40:50 -0800261 private void setWindowFlags() {
262 // Allow the activity to be shown when the screen is locked and filter out touch events that are
263 // "too fat".
264 int flags =
265 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
266 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
267
linyuhf79d1cb2017-12-15 17:49:56 -0800268 // When the audio stream is not via Bluetooth, turn on the screen once the activity is shown.
269 // When the audio stream is via Bluetooth, turn on the screen only for an incoming call.
linyuhc3968e62017-11-20 17:40:50 -0800270 final int audioRoute = getAudioRoute();
linyuhf79d1cb2017-12-15 17:49:56 -0800271 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH
272 || CallList.getInstance().getIncomingCall() != null) {
linyuhc3968e62017-11-20 17:40:50 -0800273 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
274 }
275
276 getWindow().addFlags(flags);
277 }
278
279 private static int getAudioRoute() {
280 if (audioRouteForTesting.isPresent()) {
281 return audioRouteForTesting.get();
282 }
283
284 return AudioModeProvider.getInstance().getAudioState().getRoute();
285 }
286
287 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
288 public static void setAudioRouteForTesting(int audioRoute) {
289 audioRouteForTesting = Optional.of(audioRoute);
290 }
291
292 private void internalResolveIntent(Intent intent) {
293 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
294 return;
295 }
296
297 if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) {
298 // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be
299 // initially visible. If the extra is absent, leave the dialpad in its previous state.
300 boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false);
301 relaunchedFromDialer(showDialpad);
302 }
303
304 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
305 if (outgoingCall == null) {
306 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
307 }
308 if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) {
309 intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL);
310
311 // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of
312 // making it (i.e. no valid call capable accounts).
313 // If the version is not MSIM compatible, ignore this code.
314 if (CompatUtils.isMSIMCompatible()
315 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
316 LogUtil.i(
317 "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting");
318 outgoingCall.disconnect();
319 }
320
321 dismissKeyguard(true);
322 }
323
324 if (showPhoneAccountSelectionDialog()) {
325 hideMainInCallFragment();
326 }
327 }
328
329 /**
330 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
331 * be shown on launch.
332 *
333 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
334 * false} to indicate no change should be made to the dialpad visibility.
335 */
336 private void relaunchedFromDialer(boolean showDialpad) {
337 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
338 animateDialpadOnShow = true;
339
340 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
341 // If there's only one line in use, AND it's on hold, then we're sure the user
342 // wants to use the dialpad toward the exact line, so un-hold the holding line.
343 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
344 if (call != null && call.getState() == State.ONHOLD) {
345 call.unhold();
346 }
347 }
348 }
349
350 /**
351 * Show a phone account selection dialog if there is a call waiting for phone account selection.
352 *
353 * @return true if the dialog was shown.
354 */
355 private boolean showPhoneAccountSelectionDialog() {
356 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
357 if (waitingForAccountCall == null) {
358 return false;
359 }
360
361 Bundle extras = waitingForAccountCall.getIntentExtras();
362 List<PhoneAccountHandle> phoneAccountHandles =
363 extras == null
364 ? new ArrayList<>()
365 : extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
366
367 selectPhoneAccountDialogFragment =
368 SelectPhoneAccountDialogFragment.newInstance(
369 R.string.select_phone_account_for_calls,
370 true /* canSetDefault */,
371 0 /* setDefaultResId */,
372 phoneAccountHandles,
373 selectPhoneAccountListener,
374 waitingForAccountCall.getId(),
375 null /* hints */);
376 selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
377 return true;
378 }
379
Eric Erfanianccca3152017-02-22 16:32:36 -0800380 @Override
381 protected void onSaveInstanceState(Bundle out) {
linyuh57b093b2017-11-17 14:32:32 -0800382 LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
383
384 // TODO: DialpadFragment should handle this as part of its own state
linyuhc3968e62017-11-20 17:40:50 -0800385 out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible());
linyuh57b093b2017-11-17 14:32:32 -0800386 DialpadFragment dialpadFragment = getDialpadFragment();
387 if (dialpadFragment != null) {
linyuhc3968e62017-11-20 17:40:50 -0800388 out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText());
linyuh57b093b2017-11-17 14:32:32 -0800389 }
390
linyuhc3968e62017-11-20 17:40:50 -0800391 out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
392 out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
393 out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
wangqi153af2f2018-02-15 16:21:49 -0800394 out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen);
linyuh57b093b2017-11-17 14:32:32 -0800395
Eric Erfanianccca3152017-02-22 16:32:36 -0800396 super.onSaveInstanceState(out);
397 isVisible = false;
398 }
399
400 @Override
401 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700402 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800403 super.onStart();
linyuh57b093b2017-11-17 14:32:32 -0800404
Eric Erfanianccca3152017-02-22 16:32:36 -0800405 isVisible = true;
406 showMainInCallFragment();
linyuh57b093b2017-11-17 14:32:32 -0800407
408 InCallPresenter.getInstance().setActivity(this);
409 enableInCallOrientationEventListener(
410 getRequestedOrientation()
411 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
412 InCallPresenter.getInstance().onActivityStarted();
413
yueg10f6e822018-01-17 15:32:18 -0800414 if (!isRecreating) {
415 InCallPresenter.getInstance().onUiShowing(true);
416 }
417
Eric Erfanianccca3152017-02-22 16:32:36 -0800418 if (ActivityCompat.isInMultiWindowMode(this)
419 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
420 // Hide the dialpad because there may not be enough room
421 showDialpadFragment(false, false);
422 }
linyuh57b093b2017-11-17 14:32:32 -0800423
Eric Erfanian2ca43182017-08-31 06:57:16 -0700424 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 }
426
427 @Override
428 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700429 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 super.onResume();
linyuhc3968e62017-11-20 17:40:50 -0800431
432 if (!InCallPresenter.getInstance().isReadyForTearDown()) {
433 updateTaskDescription();
yueg10f6e822018-01-17 15:32:18 -0800434 InCallPresenter.getInstance().updateNotification();
linyuhc3968e62017-11-20 17:40:50 -0800435 }
436
437 // If there is a pending request to show or hide the dialpad, handle that now.
438 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
439 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
440 // Exit fullscreen so that the user has access to the dialpad hide/show button.
441 // This is important when showing the dialpad from within dialer.
442 InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */);
443
444 showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
445 animateDialpadOnShow = false;
446
447 DialpadFragment dialpadFragment = getDialpadFragment();
448 if (dialpadFragment != null) {
449 dialpadFragment.setDtmfText(dtmfTextToPrepopulate);
450 dtmfTextToPrepopulate = null;
451 }
452 } else {
453 LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad");
454 if (getDialpadFragment() != null) {
455 showDialpadFragment(false /* show */, false /* animate */);
456 }
457 }
458 showDialpadRequest = DIALPAD_REQUEST_NONE;
459 }
460 updateNavigationBar(isDialpadVisible());
461
462 if (showPostCharWaitDialogOnResume) {
463 showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
464 }
465
466 CallList.getInstance()
467 .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false));
468
Eric Erfanianccca3152017-02-22 16:32:36 -0800469 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
470 pseudoScreenState.addListener(this);
471 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700472 Trace.endSection();
weijiaxu650e7cc2017-10-31 12:38:54 -0700473 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
474 ThreadUtil.postDelayedOnUiThread(
weijiaxuc950a9b2017-11-06 16:39:04 -0800475 () ->
zachh7a96dc72018-02-20 22:16:03 -0800476 MetricsComponent.get(this)
477 .metrics()
478 .recordMemory(Metrics.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME),
weijiaxu650e7cc2017-10-31 12:38:54 -0700479 1000);
Eric Erfanianccca3152017-02-22 16:32:36 -0800480 }
481
Eric Erfanianccca3152017-02-22 16:32:36 -0800482 @Override
483 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700484 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800485 super.onPause();
linyuh57b093b2017-11-17 14:32:32 -0800486
487 DialpadFragment dialpadFragment = getDialpadFragment();
488 if (dialpadFragment != null) {
489 dialpadFragment.onDialerKeyUp(null);
490 }
491
yueg10f6e822018-01-17 15:32:18 -0800492 InCallPresenter.getInstance().updateNotification();
linyuh57b093b2017-11-17 14:32:32 -0800493
Eric Erfanianccca3152017-02-22 16:32:36 -0800494 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700495 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800496 }
497
498 @Override
499 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700500 Trace.beginSection("InCallActivity.onStop");
wangqi4d705e52017-09-28 12:23:35 -0700501 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800502 super.onStop();
linyuh57b093b2017-11-17 14:32:32 -0800503
504 // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
505 // user presses the home button).
506 // Without this the pending call will get stuck on phone account selection and new calls can't
507 // be created.
508 // Skip this when the screen is locked since the activity may complete its current life cycle
509 // and restart.
linyuhc3968e62017-11-20 17:40:50 -0800510 if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
linyuh57b093b2017-11-17 14:32:32 -0800511 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
512 if (waitingForAccountCall != null) {
513 waitingForAccountCall.disconnect();
514 }
515 }
516
517 enableInCallOrientationEventListener(false);
518 InCallPresenter.getInstance().updateIsChangingConfigurations();
519 InCallPresenter.getInstance().onActivityStopped();
linyuhc3968e62017-11-20 17:40:50 -0800520 if (!isRecreating) {
yueg10f6e822018-01-17 15:32:18 -0800521 InCallPresenter.getInstance().onUiShowing(false);
linyuh57b093b2017-11-17 14:32:32 -0800522 if (errorDialog != null) {
523 errorDialog.dismiss();
524 }
525 }
526
yueg10f6e822018-01-17 15:32:18 -0800527 if (isFinishing()) {
528 InCallPresenter.getInstance().unsetActivity(this);
529 }
530
Eric Erfanian2ca43182017-08-31 06:57:16 -0700531 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800532 }
533
534 @Override
535 protected void onDestroy() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700536 Trace.beginSection("InCallActivity.onDestroy");
Eric Erfanianccca3152017-02-22 16:32:36 -0800537 super.onDestroy();
linyuh57b093b2017-11-17 14:32:32 -0800538
539 InCallPresenter.getInstance().unsetActivity(this);
540 InCallPresenter.getInstance().updateIsChangingConfigurations();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700541 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 }
543
544 @Override
545 public void finish() {
546 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700547 // When user select incall ui from recents after the call is disconnected, it tries to launch
548 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
549 // crash.
550 // By calling finishAndRemoveTask() instead of finish() the task associated with
551 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
552 // this case.
553 //
554 // Calling finish won't clear the task and normally when an activity finishes it shouldn't
555 // clear the task since there could be parent activity in the same task that's still alive.
556 // But InCallActivity is special since it's singleInstance which means it's root activity and
557 // only instance of activity in the task. So it should be safe to also remove task when
558 // finishing.
559 // It's also necessary in the sense of it's excluded from recents. So whenever the activity
560 // finishes, the task should also be removed since it doesn't make sense to go back to it in
561 // anyway anymore.
562 super.finishAndRemoveTask();
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 }
564 }
565
566 private boolean shouldCloseActivityOnFinish() {
linyuhc3968e62017-11-20 17:40:50 -0800567 if (!isVisible) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 LogUtil.i(
569 "InCallActivity.shouldCloseActivityOnFinish",
570 "allowing activity to be closed because it's not visible");
571 return true;
572 }
573
twyen8efb4952017-10-06 16:35:54 -0700574 if (InCallPresenter.getInstance().isInCallUiLocked()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 LogUtil.i(
576 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700577 "in call ui is locked, not closing activity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800578 return false;
579 }
580
581 LogUtil.i(
582 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700583 "activity is visible and has no locks, allowing activity to close");
Eric Erfanianccca3152017-02-22 16:32:36 -0800584 return true;
585 }
586
587 @Override
588 protected void onNewIntent(Intent intent) {
linyuhc3968e62017-11-20 17:40:50 -0800589 LogUtil.enterBlock("InCallActivity.onNewIntent");
Eric Erfanianccca3152017-02-22 16:32:36 -0800590
591 // If the screen is off, we need to make sure it gets turned on for incoming calls.
592 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
593 // when the activity is first created. Therefore, to ensure the screen is turned on
594 // for the call waiting case, we recreate() the current activity. There should be no jank from
595 // this since the screen is already off and will remain so until our new activity is up.
linyuhc3968e62017-11-20 17:40:50 -0800596 if (!isVisible) {
597 onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800598 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
599 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700600 } else {
linyuhc3968e62017-11-20 17:40:50 -0800601 onNewIntent(intent, false /* isRecreating */);
602 }
603 }
604
yuega3305352018-01-09 11:02:47 -0800605 @VisibleForTesting
606 void onNewIntent(Intent intent, boolean isRecreating) {
linyuhc3968e62017-11-20 17:40:50 -0800607 this.isRecreating = isRecreating;
608
609 // We're being re-launched with a new Intent. Since it's possible for a single InCallActivity
610 // instance to persist indefinitely (even if we finish() ourselves), this sequence can
611 // happen any time the InCallActivity needs to be displayed.
612
613 // Stash away the new intent so that we can get it in the future by calling getIntent().
614 // Otherwise getIntent() will return the original Intent from when we first got created.
615 setIntent(intent);
616
617 // Activities are always paused before receiving a new intent, so we can count on our onResume()
618 // method being called next.
619
620 // Just like in onCreate(), handle the intent.
621 // Skip if InCallActivity is going to be recreated since this will be called in onCreate().
622 if (!isRecreating) {
623 internalResolveIntent(intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800624 }
625 }
626
627 @Override
628 public void onBackPressed() {
linyuh57b093b2017-11-17 14:32:32 -0800629 LogUtil.enterBlock("InCallActivity.onBackPressed");
630
linyuhc3968e62017-11-20 17:40:50 -0800631 if (!isVisible) {
linyuh57b093b2017-11-17 14:32:32 -0800632 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800633 }
linyuh57b093b2017-11-17 14:32:32 -0800634
635 if (!getCallCardFragmentVisible()) {
636 return;
637 }
638
639 DialpadFragment dialpadFragment = getDialpadFragment();
640 if (dialpadFragment != null && dialpadFragment.isVisible()) {
641 showDialpadFragment(false /* show */, true /* animate */);
642 return;
643 }
644
645 if (CallList.getInstance().getIncomingCall() != null) {
646 LogUtil.i(
647 "InCallActivity.onBackPressed",
648 "Ignore the press of the back key when an incoming call is ringing");
649 return;
650 }
651
652 // Nothing special to do. Fall back to the default behavior.
653 super.onBackPressed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800654 }
655
656 @Override
657 public boolean onOptionsItemSelected(MenuItem item) {
658 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
659 if (item.getItemId() == android.R.id.home) {
660 onBackPressed();
661 return true;
662 }
663 return super.onOptionsItemSelected(item);
664 }
665
666 @Override
667 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800668 DialpadFragment dialpadFragment = getDialpadFragment();
669 if (dialpadFragment != null
670 && dialpadFragment.isVisible()
671 && dialpadFragment.onDialerKeyUp(event)) {
672 return true;
673 }
674
675 if (keyCode == KeyEvent.KEYCODE_CALL) {
676 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
677 return true;
678 }
679
680 return super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800681 }
682
683 @Override
684 public boolean onKeyDown(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800685 switch (keyCode) {
686 case KeyEvent.KEYCODE_CALL:
687 if (!InCallPresenter.getInstance().handleCallKey()) {
688 LogUtil.e(
689 "InCallActivity.onKeyDown",
690 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
691 }
692 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
693 return true;
694
695 // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
696 // is exactly what's needed, namely
697 // (1) "hang up" if there's an active call, or
698 // (2) "don't answer" if there's an incoming call.
699 // (See PhoneWindowManager for implementation details.)
700
701 case KeyEvent.KEYCODE_CAMERA:
702 // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
703 return true;
704
705 case KeyEvent.KEYCODE_VOLUME_UP:
706 case KeyEvent.KEYCODE_VOLUME_DOWN:
707 case KeyEvent.KEYCODE_VOLUME_MUTE:
708 // Ringer silencing handled by PhoneWindowManager.
709 break;
710
711 case KeyEvent.KEYCODE_MUTE:
712 TelecomAdapter.getInstance()
713 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
714 return true;
715
716 case KeyEvent.KEYCODE_SLASH:
717 // When verbose logging is enabled, dump the view for debugging/testing purposes.
718 if (LogUtil.isVerboseEnabled()) {
719 View decorView = getWindow().getDecorView();
720 LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
721 return true;
722 }
723 break;
724
725 case KeyEvent.KEYCODE_EQUALS:
726 break;
727
728 default: // fall out
729 }
730
731 // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
732 // in DTMF (Dual-tone multi-frequency signaling) code.
733 DialpadFragment dialpadFragment = getDialpadFragment();
734 if (dialpadFragment != null
735 && dialpadFragment.isVisible()
736 && dialpadFragment.onDialerKeyDown(event)) {
737 return true;
738 }
739
740 return super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800741 }
742
743 public boolean isInCallScreenAnimating() {
744 return false;
745 }
746
747 public void showConferenceFragment(boolean show) {
748 if (show) {
749 startActivity(new Intent(this, ManageConferenceActivity.class));
750 }
751 }
752
linyuhc3968e62017-11-20 17:40:50 -0800753 public void showDialpadFragment(boolean show, boolean animate) {
754 if (show == isDialpadVisible()) {
755 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 }
linyuhc3968e62017-11-20 17:40:50 -0800757
758 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
759 if (dialpadFragmentManager == null) {
760 LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager");
761 return;
762 }
763
764 if (!animate) {
765 if (show) {
766 showDialpadFragment();
767 } else {
768 hideDialpadFragment();
769 }
770 } else {
771 if (show) {
772 showDialpadFragment();
773 getDialpadFragment().animateShowDialpad();
774 }
775 getDialpadFragment()
776 .getView()
777 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
778 }
779
780 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
781 if (sensor != null) {
782 sensor.onDialpadVisible(show);
783 }
784 showDialpadRequest = DIALPAD_REQUEST_NONE;
785
786 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
787 // repositions itself.
788 getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
789 }
790
791 private void showDialpadFragment() {
792 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
793 if (dialpadFragmentManager == null) {
794 return;
795 }
796
797 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
798 DialpadFragment dialpadFragment = getDialpadFragment();
799 if (dialpadFragment == null) {
800 transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT);
801 } else {
802 transaction.show(dialpadFragment);
calderwoodrad5883872017-12-12 15:29:12 -0800803 dialpadFragment.setUserVisibleHint(true);
linyuhc3968e62017-11-20 17:40:50 -0800804 }
805 transaction.commitAllowingStateLoss();
806 dialpadFragmentManager.executePendingTransactions();
807
808 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this);
809 updateNavigationBar(true /* isDialpadVisible */);
810 }
811
812 private void hideDialpadFragment() {
813 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
814 if (dialpadFragmentManager == null) {
815 return;
816 }
817
calderwoodrad5883872017-12-12 15:29:12 -0800818 DialpadFragment dialpadFragment = getDialpadFragment();
linyuhc3968e62017-11-20 17:40:50 -0800819 if (dialpadFragment != null) {
820 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
821 transaction.hide(dialpadFragment);
822 transaction.commitAllowingStateLoss();
823 dialpadFragmentManager.executePendingTransactions();
calderwoodrad5883872017-12-12 15:29:12 -0800824 dialpadFragment.setUserVisibleHint(false);
linyuhc3968e62017-11-20 17:40:50 -0800825 }
826 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 }
828
829 public boolean isDialpadVisible() {
linyuh69a25062017-11-15 16:18:51 -0800830 DialpadFragment dialpadFragment = getDialpadFragment();
calderwoodraa584bcd2018-01-24 12:19:56 -0800831 return dialpadFragment != null
832 && dialpadFragment.isAdded()
833 && !dialpadFragment.isHidden()
834 && dialpadFragment.getView() != null
835 && dialpadFragment.getUserVisibleHint();
linyuh69a25062017-11-15 16:18:51 -0800836 }
837
linyuhc3968e62017-11-20 17:40:50 -0800838 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
linyuh69a25062017-11-15 16:18:51 -0800839 @Nullable
linyuhc3968e62017-11-20 17:40:50 -0800840 private DialpadFragment getDialpadFragment() {
linyuh69a25062017-11-15 16:18:51 -0800841 FragmentManager fragmentManager = getDialpadFragmentManager();
842 if (fragmentManager == null) {
843 return null;
844 }
linyuhc3968e62017-11-20 17:40:50 -0800845 return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800846 }
847
848 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh57b093b2017-11-17 14:32:32 -0800849 updateTaskDescription();
850
851 if (newForegroundCall == null || !didShowAnswerScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800852 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
linyuh57b093b2017-11-17 14:32:32 -0800853 updateWindowBackgroundColor(0 /* progress */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800854 }
855 }
856
linyuhc3968e62017-11-20 17:40:50 -0800857 private void updateTaskDescription() {
linyuh57b093b2017-11-17 14:32:32 -0800858 int color =
859 getResources().getBoolean(R.bool.is_layout_landscape)
860 ? ResourcesCompat.getColor(
861 getResources(), R.color.statusbar_background_color, getTheme())
862 : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
863 setTaskDescription(
864 new TaskDescription(
865 getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
866 }
867
Eric Erfanianccca3152017-02-22 16:32:36 -0800868 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
869 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
870 @ColorInt int top;
871 @ColorInt int middle;
872 @ColorInt int bottom;
873 @ColorInt int gray = 0x66000000;
874
875 if (ActivityCompat.isInMultiWindowMode(this)) {
876 top = themeColorManager.getBackgroundColorSolid();
877 middle = themeColorManager.getBackgroundColorSolid();
878 bottom = themeColorManager.getBackgroundColorSolid();
879 } else {
880 top = themeColorManager.getBackgroundColorTop();
881 middle = themeColorManager.getBackgroundColorMiddle();
882 bottom = themeColorManager.getBackgroundColorBottom();
883 }
884
885 if (progress < 0) {
886 float correctedProgress = Math.abs(progress);
887 top = ColorUtils.blendARGB(top, gray, correctedProgress);
888 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
889 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
890 }
891
892 boolean backgroundDirty = false;
893 if (backgroundDrawable == null) {
894 backgroundDrawableColors = new int[] {top, middle, bottom};
895 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
896 backgroundDirty = true;
897 } else {
898 if (backgroundDrawableColors[0] != top) {
899 backgroundDrawableColors[0] = top;
900 backgroundDirty = true;
901 }
902 if (backgroundDrawableColors[1] != middle) {
903 backgroundDrawableColors[1] = middle;
904 backgroundDirty = true;
905 }
906 if (backgroundDrawableColors[2] != bottom) {
907 backgroundDrawableColors[2] = bottom;
908 backgroundDirty = true;
909 }
910 if (backgroundDirty) {
911 backgroundDrawable.setColors(backgroundDrawableColors);
912 }
913 }
914
915 if (backgroundDirty) {
916 getWindow().setBackgroundDrawable(backgroundDrawable);
917 }
918 }
919
920 public boolean isVisible() {
921 return isVisible;
922 }
923
924 public boolean getCallCardFragmentVisible() {
925 return didShowInCallScreen || didShowVideoCallScreen;
926 }
927
928 public void dismissKeyguard(boolean dismiss) {
linyuh9c327da2017-11-14 12:33:48 -0800929 if (dismissKeyguard == dismiss) {
930 return;
931 }
932
933 dismissKeyguard = dismiss;
934 if (dismiss) {
935 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
936 } else {
937 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
938 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800939 }
940
linyuhc3968e62017-11-20 17:40:50 -0800941 public void showDialogForPostCharWait(String callId, String chars) {
942 if (isVisible) {
943 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
944 fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT);
945
946 showPostCharWaitDialogOnResume = false;
947 showPostCharWaitDialogCallId = null;
948 showPostCharWaitDialogChars = null;
949 } else {
950 showPostCharWaitDialogOnResume = true;
951 showPostCharWaitDialogCallId = callId;
952 showPostCharWaitDialogChars = chars;
953 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800954 }
955
linyuh7b86f562017-11-16 11:24:09 -0800956 public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
957 LogUtil.i(
958 "InCallActivity.showDialogOrToastForDisconnectedCall",
959 "disconnect cause: %s",
960 disconnectMessage);
961
962 if (disconnectMessage.dialog == null || isFinishing()) {
963 return;
964 }
965
966 dismissPendingDialogs();
967
968 // Show a toast if the app is in background when a dialog can't be visible.
969 if (!isVisible()) {
970 Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
971 .show();
972 return;
973 }
974
975 // Show the dialog.
linyuhc3968e62017-11-20 17:40:50 -0800976 errorDialog = disconnectMessage.dialog;
linyuh7b86f562017-11-16 11:24:09 -0800977 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
978 disconnectMessage.dialog.setOnDismissListener(
979 dialogInterface -> {
980 lock.release();
981 onDialogDismissed();
982 });
983 disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
984 disconnectMessage.dialog.show();
985 }
986
987 private void onDialogDismissed() {
linyuhc3968e62017-11-20 17:40:50 -0800988 errorDialog = null;
linyuh7b86f562017-11-16 11:24:09 -0800989 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800990 }
991
992 public void dismissPendingDialogs() {
linyuhc3968e62017-11-20 17:40:50 -0800993 LogUtil.enterBlock("InCallActivity.dismissPendingDialogs");
linyuhf99f6302017-11-15 11:23:51 -0800994
995 if (!isVisible) {
996 // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
997 // been called.
Eric Erfanianccca3152017-02-22 16:32:36 -0800998 LogUtil.i(
999 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
1000 needDismissPendingDialogs = true;
linyuhf99f6302017-11-15 11:23:51 -08001001 return;
Eric Erfanianccca3152017-02-22 16:32:36 -08001002 }
linyuhf99f6302017-11-15 11:23:51 -08001003
1004 // Dismiss the error dialog
linyuhf99f6302017-11-15 11:23:51 -08001005 if (errorDialog != null) {
1006 errorDialog.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001007 errorDialog = null;
linyuhf99f6302017-11-15 11:23:51 -08001008 }
1009
1010 // Dismiss the phone account selection dialog
linyuhf99f6302017-11-15 11:23:51 -08001011 if (selectPhoneAccountDialogFragment != null) {
1012 selectPhoneAccountDialogFragment.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001013 selectPhoneAccountDialogFragment = null;
linyuhf99f6302017-11-15 11:23:51 -08001014 }
1015
1016 // Dismiss the dialog for international call on WiFi
1017 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
1018 (InternationalCallOnWifiDialogFragment)
linyuhc3968e62017-11-20 17:40:50 -08001019 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
linyuhf99f6302017-11-15 11:23:51 -08001020 if (internationalCallOnWifiFragment != null) {
1021 internationalCallOnWifiFragment.dismiss();
1022 }
1023
1024 // Dismiss the answer screen
1025 AnswerScreen answerScreen = getAnswerScreen();
1026 if (answerScreen != null) {
1027 answerScreen.dismissPendingDialogs();
1028 }
1029
1030 needDismissPendingDialogs = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001031 }
1032
linyuhc3968e62017-11-20 17:40:50 -08001033 private void enableInCallOrientationEventListener(boolean enable) {
linyuh69a25062017-11-15 16:18:51 -08001034 if (enable) {
1035 inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
1036 } else {
1037 inCallOrientationEventListener.disable();
1038 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001039 }
1040
1041 public void setExcludeFromRecents(boolean exclude) {
linyuhc3968e62017-11-20 17:40:50 -08001042 int taskId = getTaskId();
1043
1044 List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks();
1045 for (AppTask task : tasks) {
1046 try {
1047 if (task.getTaskInfo().id == taskId) {
1048 task.setExcludeFromRecents(exclude);
1049 }
1050 } catch (RuntimeException e) {
1051 LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e);
1052 }
1053 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001054 }
1055
Eric Erfanianccca3152017-02-22 16:32:36 -08001056 @Nullable
1057 public FragmentManager getDialpadFragmentManager() {
1058 InCallScreen inCallScreen = getInCallScreen();
1059 if (inCallScreen != null) {
1060 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
1061 }
1062 return null;
1063 }
1064
1065 public int getDialpadContainerId() {
1066 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
1067 }
1068
1069 @Override
1070 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
1071 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
1072 if (call == null) {
1073 // This is a work around for a bug where we attempt to create a new delegate after the call
1074 // has already been removed. An example of when this can happen is:
1075 // 1. incoming video call in landscape mode
1076 // 2. remote party hangs up
1077 // 3. activity switches from landscape to portrait
1078 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
1079 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
1080 // because this new state is transient and the activity will be destroyed soon.
1081 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
1082 return new AnswerScreenPresenterStub();
1083 } else {
1084 return new AnswerScreenPresenter(
1085 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
1086 }
1087 }
1088
1089 @Override
1090 public InCallScreenDelegate newInCallScreenDelegate() {
1091 return new CallCardPresenter(this);
1092 }
1093
1094 @Override
1095 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
1096 return new CallButtonPresenter(this);
1097 }
1098
1099 @Override
Eric Erfanian90508232017-03-24 09:31:16 -07001100 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
1101 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
1102 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
1103 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
1104 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001105 return new VideoCallPresenter();
1106 }
1107
1108 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001109 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -08001110 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001111 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001112 }
1113
linyuh7b86f562017-11-16 11:24:09 -08001114 public void showToastForWiFiToLteHandover(DialerCall call) {
1115 if (call.hasShownWiFiToLteHandoverToast()) {
1116 return;
1117 }
1118
1119 Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
1120 call.setHasShownWiFiToLteHandoverToast();
Eric Erfanianccca3152017-02-22 16:32:36 -08001121 }
1122
linyuh7b86f562017-11-16 11:24:09 -08001123 public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
1124 if (call.showWifiHandoverAlertAsToast()) {
1125 Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
1126 .show();
1127 return;
1128 }
1129
1130 dismissPendingDialogs();
1131
1132 AlertDialog.Builder builder =
1133 new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
1134
1135 // This allows us to use the theme of the dialog instead of the activity
1136 View dialogCheckBoxView =
1137 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
1138 CheckBox wifiHandoverFailureCheckbox =
1139 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
1140 wifiHandoverFailureCheckbox.setChecked(false);
1141
1142 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
linyuhc3968e62017-11-20 17:40:50 -08001143 errorDialog =
linyuh7b86f562017-11-16 11:24:09 -08001144 builder
1145 .setView(dialogCheckBoxView)
1146 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
1147 .setOnCancelListener(dialogInterface -> onDialogDismissed())
1148 .setPositiveButton(
1149 android.R.string.ok,
1150 (dialogInterface, id) -> {
1151 call.setDoNotShowDialogForHandoffToWifiFailure(
1152 wifiHandoverFailureCheckbox.isChecked());
1153 dialogInterface.cancel();
1154 onDialogDismissed();
1155 })
1156 .setOnDismissListener(dialogInterface -> lock.release())
1157 .create();
linyuh7b86f562017-11-16 11:24:09 -08001158 errorDialog.show();
Eric Erfanianccca3152017-02-22 16:32:36 -08001159 }
1160
linyuh7b86f562017-11-16 11:24:09 -08001161 public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
1162 if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
1163 LogUtil.i(
1164 "InCallActivity.showDialogForInternationalCallOnWifi",
1165 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
1166 return;
1167 }
1168
1169 InternationalCallOnWifiDialogFragment fragment =
1170 InternationalCallOnWifiDialogFragment.newInstance(
linyuhc3968e62017-11-20 17:40:50 -08001171 call.getId(), internationalCallOnWifiCallback);
1172 fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI);
Eric Erfanianc857f902017-05-15 14:05:33 -07001173 }
1174
Eric Erfanian938468d2017-10-24 14:05:52 -07001175 @Override
1176 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
1177 super.onMultiWindowModeChanged(isInMultiWindowMode);
linyuh57b093b2017-11-17 14:32:32 -08001178 updateNavigationBar(isDialpadVisible());
1179 }
1180
linyuhc3968e62017-11-20 17:40:50 -08001181 private void updateNavigationBar(boolean isDialpadVisible) {
linyuh57b093b2017-11-17 14:32:32 -08001182 if (ActivityCompat.isInMultiWindowMode(this)) {
1183 return;
1184 }
1185
1186 View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
1187 if (navigationBarBackground != null) {
1188 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
Eric Erfanian938468d2017-10-24 14:05:52 -07001189 }
1190 }
1191
Eric Erfanianccca3152017-02-22 16:32:36 -08001192 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -07001193 if (this.allowOrientationChange == allowOrientationChange) {
1194 return;
1195 }
1196 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001197 if (!allowOrientationChange) {
1198 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
1199 } else {
1200 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
1201 }
1202 enableInCallOrientationEventListener(allowOrientationChange);
1203 }
1204
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001205 public void hideMainInCallFragment() {
linyuhc3968e62017-11-20 17:40:50 -08001206 LogUtil.enterBlock("InCallActivity.hideMainInCallFragment");
1207 if (getCallCardFragmentVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001208 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1209 hideInCallScreenFragment(transaction);
1210 hideVideoCallScreenFragment(transaction);
1211 transaction.commitAllowingStateLoss();
1212 getSupportFragmentManager().executePendingTransactions();
1213 }
1214 }
1215
1216 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001217 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -08001218 // If the activity's onStart method hasn't been called yet then defer doing any work.
1219 if (!isVisible) {
1220 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001221 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001222 return;
1223 }
1224
1225 // Don't let this be reentrant.
1226 if (isInShowMainInCallFragment) {
1227 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001228 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001229 return;
1230 }
1231
1232 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001233 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
1234 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
wangqi219b8702018-02-13 09:34:41 -08001235 ShouldShowUiResult shouldShowRttUi = getShouldShowRttUi();
Eric Erfanianccca3152017-02-22 16:32:36 -08001236 LogUtil.i(
1237 "InCallActivity.showMainInCallFragment",
wangqi219b8702018-02-13 09:34:41 -08001238 "shouldShowAnswerUi: %b, shouldShowRttUi: %b, shouldShowVideoUi: %b "
1239 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowRttCallScreen: %b, "
1240 + "didShowVideoCallScreen: %b",
Eric Erfanianccca3152017-02-22 16:32:36 -08001241 shouldShowAnswerUi.shouldShow,
wangqi219b8702018-02-13 09:34:41 -08001242 shouldShowRttUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -07001243 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -08001244 didShowAnswerScreen,
1245 didShowInCallScreen,
wangqi219b8702018-02-13 09:34:41 -08001246 didShowRttCallScreen,
Eric Erfanianccca3152017-02-22 16:32:36 -08001247 didShowVideoCallScreen);
1248 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001249 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -08001250
1251 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
wangqi219b8702018-02-13 09:34:41 -08001252 boolean didChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001253 if (shouldShowAnswerUi.shouldShow) {
wangqi219b8702018-02-13 09:34:41 -08001254 didChange = hideInCallScreenFragment(transaction);
1255 didChange |= hideVideoCallScreenFragment(transaction);
1256 didChange |= hideRttCallScreenFragment(transaction);
1257 didChange |= showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001258 } else if (shouldShowVideoUi.shouldShow) {
wangqi219b8702018-02-13 09:34:41 -08001259 didChange = hideInCallScreenFragment(transaction);
1260 didChange |= showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
1261 didChange |= hideRttCallScreenFragment(transaction);
1262 didChange |= hideAnswerScreenFragment(transaction);
1263 } else if (shouldShowRttUi.shouldShow) {
1264 didChange = hideInCallScreenFragment(transaction);
1265 didChange |= hideVideoCallScreenFragment(transaction);
1266 didChange |= hideAnswerScreenFragment(transaction);
1267 didChange |= showRttCallScreenFragment(transaction, shouldShowRttUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001268 } else {
wangqi219b8702018-02-13 09:34:41 -08001269 didChange = showInCallScreenFragment(transaction);
1270 didChange |= hideVideoCallScreenFragment(transaction);
1271 didChange |= hideRttCallScreenFragment(transaction);
1272 didChange |= hideAnswerScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001273 }
1274
wangqi219b8702018-02-13 09:34:41 -08001275 if (didChange) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001276 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -08001277 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001278 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001279 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1280 }
1281 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001282 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001283 }
1284
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001285 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001286 DialerCall call = CallList.getInstance().getIncomingCall();
1287 if (call != null) {
1288 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001289 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001290 }
1291
1292 call = CallList.getInstance().getVideoUpgradeRequestCall();
1293 if (call != null) {
1294 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001295 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001296 }
1297
1298 // Check if we're showing the answer screen and the call is disconnected. If this condition is
1299 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
1300 // the user rejects an incoming call.
1301 call = CallList.getInstance().getFirstCall();
1302 if (call == null) {
1303 call = CallList.getInstance().getBackgroundCall();
1304 }
1305 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
1306 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001307 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001308 }
1309
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001310 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001311 }
1312
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001313 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001314 DialerCall call = CallList.getInstance().getFirstCall();
1315 if (call == null) {
1316 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001317 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001318 }
1319
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001320 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001321 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001322 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001323 }
1324
linyuh8fbecce2017-12-18 13:53:09 -08001325 if (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001326 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001327 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001328 }
1329
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001330 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001331 }
1332
wangqi219b8702018-02-13 09:34:41 -08001333 private static ShouldShowUiResult getShouldShowRttUi() {
1334 DialerCall call = CallList.getInstance().getFirstCall();
1335 if (call == null) {
1336 LogUtil.i("InCallActivity.getShouldShowRttUi", "null call");
1337 return new ShouldShowUiResult(false, null);
1338 }
1339
1340 if (call.isRttCall()) {
1341 LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call");
1342 return new ShouldShowUiResult(true, call);
1343 }
1344
1345 if (call.hasSentRttUpgradeRequest()) {
1346 LogUtil.i("InCallActivity.getShouldShowRttUi", "upgrading to rtt");
1347 return new ShouldShowUiResult(true, call);
1348 }
1349
1350 return new ShouldShowUiResult(false, null);
1351 }
1352
Eric Erfanianccca3152017-02-22 16:32:36 -08001353 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
1354 // When rejecting a call the active call can become null in which case we should continue
1355 // showing the answer screen.
1356 if (didShowAnswerScreen && call == null) {
1357 return false;
1358 }
1359
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001360 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
1361
1362 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -08001363
1364 // Check if we're already showing an answer screen for this call.
1365 if (didShowAnswerScreen) {
1366 AnswerScreen answerScreen = getAnswerScreen();
1367 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001368 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -07001369 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
1370 && !answerScreen.isActionTimeout()) {
1371 LogUtil.d(
1372 "InCallActivity.showAnswerScreenFragment",
1373 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -08001374 return false;
1375 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001376 if (answerScreen.isActionTimeout()) {
1377 LogUtil.i(
1378 "InCallActivity.showAnswerScreenFragment",
1379 "answer fragment exists but has been accepted/rejected and timed out");
1380 } else {
1381 LogUtil.i(
1382 "InCallActivity.showAnswerScreenFragment",
1383 "answer fragment exists but arguments do not match");
1384 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001385 hideAnswerScreenFragment(transaction);
1386 }
1387
1388 // Show a new answer screen.
1389 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -07001390 AnswerBindings.createAnswerScreen(
1391 call.getId(),
wangqi219b8702018-02-13 09:34:41 -08001392 call.isRttCall(),
Eric Erfanianfc37b022017-03-21 10:11:17 -07001393 call.isVideoCall(),
1394 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -07001395 call.getVideoTech().isSelfManagedCamera(),
1396 shouldAllowAnswerAndRelease(call),
1397 CallList.getInstance().getBackgroundCall() != null);
linyuhc3968e62017-11-20 17:40:50 -08001398 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001399
1400 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
1401 didShowAnswerScreen = true;
1402 return true;
1403 }
1404
Eric Erfanian90508232017-03-24 09:31:16 -07001405 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
1406 if (CallList.getInstance().getActiveCall() == null) {
1407 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
1408 return false;
1409 }
1410 if (getSystemService(TelephonyManager.class).getPhoneType()
1411 == TelephonyManager.PHONE_TYPE_CDMA) {
1412 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
1413 return false;
1414 }
1415 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
1416 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
1417 return false;
1418 }
linyuhc3968e62017-11-20 17:40:50 -08001419 if (!ConfigProviderBindings.get(this)
1420 .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) {
Eric Erfanian90508232017-03-24 09:31:16 -07001421 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
1422 return false;
1423 }
1424
1425 return true;
1426 }
1427
Eric Erfanianccca3152017-02-22 16:32:36 -08001428 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
1429 if (!didShowAnswerScreen) {
1430 return false;
1431 }
1432 AnswerScreen answerScreen = getAnswerScreen();
1433 if (answerScreen != null) {
1434 transaction.remove(answerScreen.getAnswerScreenFragment());
1435 }
1436
1437 didShowAnswerScreen = false;
1438 return true;
1439 }
1440
1441 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
1442 if (didShowInCallScreen) {
1443 return false;
1444 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001445 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
linyuhc3968e62017-11-20 17:40:50 -08001446 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001447 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1448 didShowInCallScreen = true;
1449 return true;
1450 }
1451
1452 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
1453 if (!didShowInCallScreen) {
1454 return false;
1455 }
1456 InCallScreen inCallScreen = getInCallScreen();
1457 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001458 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -08001459 }
1460 didShowInCallScreen = false;
1461 return true;
1462 }
1463
wangqi219b8702018-02-13 09:34:41 -08001464 private boolean showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
1465 if (didShowRttCallScreen) {
1466 // This shouldn't happen since only one RTT call is allow at same time.
1467 if (!getRttCallScreen().getCallId().equals(call.getId())) {
1468 LogUtil.e("InCallActivity.showRttCallScreenFragment", "RTT call id doesn't match");
1469 }
1470 return false;
1471 }
1472 RttCallScreen rttCallScreen = RttBindings.createRttCallScreen(call.getId());
1473 transaction.add(R.id.main, rttCallScreen.getRttCallScreenFragment(), Tags.RTT_CALL_SCREEN);
1474 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1475 didShowRttCallScreen = true;
1476 return true;
1477 }
1478
1479 private boolean hideRttCallScreenFragment(FragmentTransaction transaction) {
1480 if (!didShowRttCallScreen) {
1481 return false;
1482 }
1483 RttCallScreen rttCallScreen = getRttCallScreen();
1484 if (rttCallScreen != null) {
1485 transaction.remove(rttCallScreen.getRttCallScreenFragment());
1486 }
1487 didShowRttCallScreen = false;
1488 return true;
1489 }
1490
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001491 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001492 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001493 VideoCallScreen videoCallScreen = getVideoCallScreen();
1494 if (videoCallScreen.getCallId().equals(call.getId())) {
1495 return false;
1496 }
1497 LogUtil.i(
1498 "InCallActivity.showVideoCallScreenFragment",
1499 "video call fragment exists but arguments do not match");
1500 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001501 }
1502
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001503 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
1504
Eric Erfanian90508232017-03-24 09:31:16 -07001505 VideoCallScreen videoCallScreen =
1506 VideoBindings.createVideoCallScreen(
1507 call.getId(), call.getVideoTech().shouldUseSurfaceView());
linyuhc3968e62017-11-20 17:40:50 -08001508 transaction.add(
1509 R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001510
1511 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1512 didShowVideoCallScreen = true;
1513 return true;
1514 }
1515
1516 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
1517 if (!didShowVideoCallScreen) {
1518 return false;
1519 }
1520 VideoCallScreen videoCallScreen = getVideoCallScreen();
1521 if (videoCallScreen != null) {
1522 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
1523 }
1524 didShowVideoCallScreen = false;
1525 return true;
1526 }
1527
linyuhc3968e62017-11-20 17:40:50 -08001528 private AnswerScreen getAnswerScreen() {
1529 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001530 }
1531
linyuhc3968e62017-11-20 17:40:50 -08001532 private InCallScreen getInCallScreen() {
1533 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001534 }
1535
linyuhc3968e62017-11-20 17:40:50 -08001536 private VideoCallScreen getVideoCallScreen() {
1537 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001538 }
1539
wangqi219b8702018-02-13 09:34:41 -08001540 private RttCallScreen getRttCallScreen() {
1541 return (RttCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.RTT_CALL_SCREEN);
1542 }
1543
Eric Erfanianccca3152017-02-22 16:32:36 -08001544 @Override
1545 public void onPseudoScreenStateChanged(boolean isOn) {
1546 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
1547 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
1548 }
1549
1550 /**
1551 * For some touch related issue, turning off the screen can be faked by drawing a black view over
1552 * the activity. All touch events started when the screen is "off" is rejected.
1553 *
1554 * @see PseudoScreenState
1555 */
1556 @Override
1557 public boolean dispatchTouchEvent(MotionEvent event) {
1558 // Reject any gesture that started when the screen is in the fake off state.
1559 if (touchDownWhenPseudoScreenOff) {
1560 if (event.getAction() == MotionEvent.ACTION_UP) {
1561 touchDownWhenPseudoScreenOff = false;
1562 }
1563 return true;
1564 }
1565 // Reject all touch event when the screen is in the fake off state.
1566 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
1567 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1568 touchDownWhenPseudoScreenOff = true;
1569 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
1570 }
1571 return true;
1572 }
1573 return super.dispatchTouchEvent(event);
1574 }
1575
wangqi219b8702018-02-13 09:34:41 -08001576 @Override
1577 public RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen videoCallScreen) {
1578 return new RttCallPresenter();
1579 }
1580
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001581 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -08001582 public final boolean shouldShow;
1583 public final DialerCall call;
1584
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001585 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001586 this.shouldShow = shouldShow;
1587 this.call = call;
1588 }
1589 }
linyuhc3968e62017-11-20 17:40:50 -08001590
1591 private static final class IntentExtraNames {
1592 static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent";
1593 static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
1594 static final String SHOW_DIALPAD = "InCallActivity.show_dialpad";
1595 }
1596
1597 private static final class KeysForSavedInstance {
1598 static final String DIALPAD_TEXT = "InCallActivity.dialpad_text";
1599 static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
1600 static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
1601 static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
wangqi153af2f2018-02-15 16:21:49 -08001602 static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen";
linyuhc3968e62017-11-20 17:40:50 -08001603 }
1604
1605 /** Request codes for pending intents. */
1606 public static final class PendingIntentRequestCodes {
1607 static final int NON_FULL_SCREEN = 0;
1608 static final int FULL_SCREEN = 1;
1609 static final int BUBBLE = 2;
1610 }
1611
1612 private static final class Tags {
1613 static final String ANSWER_SCREEN = "tag_answer_screen";
1614 static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment";
1615 static final String IN_CALL_SCREEN = "tag_in_call_screen";
1616 static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
1617 static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
1618 static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
wangqi219b8702018-02-13 09:34:41 -08001619 static final String RTT_CALL_SCREEN = "tag_rtt_call_screen";
linyuhc3968e62017-11-20 17:40:50 -08001620 static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
1621 }
1622
1623 private static final class ConfigNames {
1624 static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
1625 }
1626
1627 private static final class InternationalCallOnWifiCallback
1628 implements InternationalCallOnWifiDialogFragment.Callback {
1629 private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName();
1630
1631 @Override
1632 public void continueCall(@NonNull String callId) {
1633 LogUtil.i(TAG, "Continuing call with ID: %s", callId);
1634 }
1635
1636 @Override
1637 public void cancelCall(@NonNull String callId) {
1638 DialerCall call = CallList.getInstance().getCallById(callId);
1639 if (call == null) {
1640 LogUtil.i(TAG, "Call destroyed before the dialog is closed");
1641 return;
1642 }
1643
1644 LogUtil.i(TAG, "Disconnecting international call on WiFi");
1645 call.disconnect();
1646 }
1647 }
1648
1649 private static final class SelectPhoneAccountListener
1650 extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener {
1651 private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName();
1652
1653 @Override
1654 public void onPhoneAccountSelected(
1655 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
1656 DialerCall call = CallList.getInstance().getCallById(callId);
1657 LogUtil.i(TAG, "Phone account select with call:\n%s", call);
1658
1659 if (call != null) {
1660 call.phoneAccountSelected(selectedAccountHandle, setDefault);
1661 }
1662 }
1663
1664 @Override
1665 public void onDialogDismissed(String callId) {
1666 DialerCall call = CallList.getInstance().getCallById(callId);
1667 LogUtil.i(TAG, "Disconnecting call:\n%s" + call);
1668
1669 if (call != null) {
1670 call.disconnect();
1671 }
1672 }
1673 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001674}