blob: 67f5cfe4fc20595bdd1369500543f0b3311b27db [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;
weijiaxu94df7202017-10-25 18:21:41 -070064import com.android.dialer.logging.LoggingBindings;
Eric Erfanian8369df02017-05-03 10:27:13 -070065import com.android.dialer.logging.ScreenEvent;
linyuhc3968e62017-11-20 17:40:50 -080066import com.android.dialer.util.ViewUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.answer.bindings.AnswerBindings;
68import com.android.incallui.answer.protocol.AnswerScreen;
69import com.android.incallui.answer.protocol.AnswerScreenDelegate;
70import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
71import com.android.incallui.answerproximitysensor.PseudoScreenState;
linyuh57b093b2017-11-17 14:32:32 -080072import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080073import com.android.incallui.call.CallList;
74import com.android.incallui.call.DialerCall;
75import com.android.incallui.call.DialerCall.State;
linyuh57b093b2017-11-17 14:32:32 -080076import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070077import com.android.incallui.callpending.CallPendingActivity;
78import com.android.incallui.disconnectdialog.DisconnectMessage;
Eric Erfanianccca3152017-02-22 16:32:36 -080079import com.android.incallui.incall.bindings.InCallBindings;
80import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
81import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
82import com.android.incallui.incall.protocol.InCallScreen;
83import com.android.incallui.incall.protocol.InCallScreenDelegate;
84import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
linyuh7b86f562017-11-16 11:24:09 -080085import com.android.incallui.incalluilock.InCallUiLock;
wangqi219b8702018-02-13 09:34:41 -080086import com.android.incallui.rtt.bindings.RttBindings;
87import com.android.incallui.rtt.protocol.RttCallScreen;
88import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
89import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
linyuhf99f6302017-11-15 11:23:51 -080090import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080091import com.android.incallui.video.bindings.VideoBindings;
92import com.android.incallui.video.protocol.VideoCallScreen;
93import com.android.incallui.video.protocol.VideoCallScreenDelegate;
94import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
linyuhc3968e62017-11-20 17:40:50 -080095import com.google.common.base.Optional;
96import java.lang.annotation.Retention;
97import java.lang.annotation.RetentionPolicy;
98import java.util.ArrayList;
99import java.util.List;
Eric Erfanianccca3152017-02-22 16:32:36 -0800100
101/** Version of {@link InCallActivity} that shows the new UI */
102public class InCallActivity extends TransactionSafeFragmentActivity
103 implements AnswerScreenDelegateFactory,
104 InCallScreenDelegateFactory,
105 InCallButtonUiDelegateFactory,
106 VideoCallScreenDelegateFactory,
wangqi219b8702018-02-13 09:34:41 -0800107 RttCallScreenDelegateFactory,
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 PseudoScreenState.StateChangedListener {
109
linyuhc3968e62017-11-20 17:40:50 -0800110 @Retention(RetentionPolicy.SOURCE)
111 @IntDef({
112 DIALPAD_REQUEST_NONE,
113 DIALPAD_REQUEST_SHOW,
114 DIALPAD_REQUEST_HIDE,
115 })
116 @interface DialpadRequestType {}
Eric Erfanian2ca43182017-08-31 06:57:16 -0700117
linyuhc3968e62017-11-20 17:40:50 -0800118 private static final int DIALPAD_REQUEST_NONE = 1;
119 private static final int DIALPAD_REQUEST_SHOW = 2;
120 private static final int DIALPAD_REQUEST_HIDE = 3;
linyuh57b093b2017-11-17 14:32:32 -0800121
linyuhc3968e62017-11-20 17:40:50 -0800122 private static Optional<Integer> audioRouteForTesting = Optional.absent();
linyuh57b093b2017-11-17 14:32:32 -0800123
linyuhc3968e62017-11-20 17:40:50 -0800124 private final InternationalCallOnWifiCallback internationalCallOnWifiCallback =
125 new InternationalCallOnWifiCallback();
126 private final SelectPhoneAccountListener selectPhoneAccountListener =
127 new SelectPhoneAccountListener();
Eric Erfanianccca3152017-02-22 16:32:36 -0800128
linyuhc3968e62017-11-20 17:40:50 -0800129 private Animation dialpadSlideInAnimation;
130 private Animation dialpadSlideOutAnimation;
131 private Dialog errorDialog;
132 private GradientDrawable backgroundDrawable;
linyuh69a25062017-11-15 16:18:51 -0800133 private InCallOrientationEventListener inCallOrientationEventListener;
linyuhc3968e62017-11-20 17:40:50 -0800134 private View pseudoBlackScreenOverlay;
135 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
136 private String dtmfTextToPrepopulate;
137 private String showPostCharWaitDialogCallId;
138 private String showPostCharWaitDialogChars;
139 private boolean allowOrientationChange;
140 private boolean animateDialpadOnShow;
Eric Erfanianccca3152017-02-22 16:32:36 -0800141 private boolean didShowAnswerScreen;
142 private boolean didShowInCallScreen;
143 private boolean didShowVideoCallScreen;
wangqi219b8702018-02-13 09:34:41 -0800144 private boolean didShowRttCallScreen;
linyuh9c327da2017-11-14 12:33:48 -0800145 private boolean dismissKeyguard;
Eric Erfanianccca3152017-02-22 16:32:36 -0800146 private boolean isInShowMainInCallFragment;
linyuhc3968e62017-11-20 17:40:50 -0800147 private boolean isRecreating; // whether the activity is going to be recreated
148 private boolean isVisible;
Eric Erfanianccca3152017-02-22 16:32:36 -0800149 private boolean needDismissPendingDialogs;
linyuhc3968e62017-11-20 17:40:50 -0800150 private boolean showPostCharWaitDialogOnResume;
151 private boolean touchDownWhenPseudoScreenOff;
152 private int[] backgroundDrawableColors;
153 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800154
155 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700156 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800157 Intent intent = new Intent(Intent.ACTION_MAIN, null);
158 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
159 intent.setClass(context, InCallActivity.class);
linyuhc3968e62017-11-20 17:40:50 -0800160 if (showDialpad) {
161 intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true);
162 }
163 intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall);
164 intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800165 return intent;
166 }
167
168 @Override
169 protected void onResumeFragments() {
170 super.onResumeFragments();
171 if (needDismissPendingDialogs) {
172 dismissPendingDialogs();
173 }
174 }
175
176 @Override
linyuhc3968e62017-11-20 17:40:50 -0800177 protected void onCreate(Bundle bundle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700178 Trace.beginSection("InCallActivity.onCreate");
linyuhc3968e62017-11-20 17:40:50 -0800179 super.onCreate(bundle);
Eric Erfanianccca3152017-02-22 16:32:36 -0800180
linyuhc3968e62017-11-20 17:40:50 -0800181 if (bundle != null) {
182 didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
183 didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
184 didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800185 }
186
linyuhc3968e62017-11-20 17:40:50 -0800187 setWindowFlags();
188 setContentView(R.layout.incall_screen);
189 internalResolveIntent(getIntent());
190
191 boolean isLandscape =
192 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
193 boolean isRtl = ViewUtil.isRtl();
194 if (isLandscape) {
195 dialpadSlideInAnimation =
196 AnimationUtils.loadAnimation(
197 this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
198 dialpadSlideOutAnimation =
199 AnimationUtils.loadAnimation(
200 this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
201 } else {
202 dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
203 dialpadSlideOutAnimation =
204 AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
205 }
206 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
207 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
208 dialpadSlideOutAnimation.setAnimationListener(
209 new AnimationListenerAdapter() {
210 @Override
211 public void onAnimationEnd(Animation animation) {
212 hideDialpadFragment();
213 }
214 });
215
216 if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
217 // If the dialpad was shown before, set related variables so that it can be shown and
218 // populated with the previous DTMF text during onResume().
219 if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) {
220 boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD);
221 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
222 animateDialpadOnShow = false;
223 }
224 dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT);
225
226 SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
227 (SelectPhoneAccountDialogFragment)
228 getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT);
229 if (selectPhoneAccountDialogFragment != null) {
230 selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener);
231 }
232 }
233
234 InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment =
235 (InternationalCallOnWifiDialogFragment)
236 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
237 if (existingInternationalCallOnWifiDialogFragment != null) {
238 existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback);
239 }
240
linyuh69a25062017-11-15 16:18:51 -0800241 inCallOrientationEventListener = new InCallOrientationEventListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800242
243 getWindow()
244 .getDecorView()
245 .setSystemUiVisibility(
246 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
247
248 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700249 sendBroadcast(CallPendingActivity.getFinishBroadcast());
250 Trace.endSection();
weijiaxuc950a9b2017-11-06 16:39:04 -0800251 Logger.get(this)
252 .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
253 Logger.get(this)
254 .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800255 }
256
linyuhc3968e62017-11-20 17:40:50 -0800257 private void setWindowFlags() {
258 // Allow the activity to be shown when the screen is locked and filter out touch events that are
259 // "too fat".
260 int flags =
261 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
262 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
263
linyuhf79d1cb2017-12-15 17:49:56 -0800264 // When the audio stream is not via Bluetooth, turn on the screen once the activity is shown.
265 // When the audio stream is via Bluetooth, turn on the screen only for an incoming call.
linyuhc3968e62017-11-20 17:40:50 -0800266 final int audioRoute = getAudioRoute();
linyuhf79d1cb2017-12-15 17:49:56 -0800267 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH
268 || CallList.getInstance().getIncomingCall() != null) {
linyuhc3968e62017-11-20 17:40:50 -0800269 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
270 }
271
272 getWindow().addFlags(flags);
273 }
274
275 private static int getAudioRoute() {
276 if (audioRouteForTesting.isPresent()) {
277 return audioRouteForTesting.get();
278 }
279
280 return AudioModeProvider.getInstance().getAudioState().getRoute();
281 }
282
283 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
284 public static void setAudioRouteForTesting(int audioRoute) {
285 audioRouteForTesting = Optional.of(audioRoute);
286 }
287
288 private void internalResolveIntent(Intent intent) {
289 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
290 return;
291 }
292
293 if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) {
294 // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be
295 // initially visible. If the extra is absent, leave the dialpad in its previous state.
296 boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false);
297 relaunchedFromDialer(showDialpad);
298 }
299
300 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
301 if (outgoingCall == null) {
302 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
303 }
304 if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) {
305 intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL);
306
307 // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of
308 // making it (i.e. no valid call capable accounts).
309 // If the version is not MSIM compatible, ignore this code.
310 if (CompatUtils.isMSIMCompatible()
311 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
312 LogUtil.i(
313 "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting");
314 outgoingCall.disconnect();
315 }
316
317 dismissKeyguard(true);
318 }
319
320 if (showPhoneAccountSelectionDialog()) {
321 hideMainInCallFragment();
322 }
323 }
324
325 /**
326 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
327 * be shown on launch.
328 *
329 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
330 * false} to indicate no change should be made to the dialpad visibility.
331 */
332 private void relaunchedFromDialer(boolean showDialpad) {
333 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
334 animateDialpadOnShow = true;
335
336 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
337 // If there's only one line in use, AND it's on hold, then we're sure the user
338 // wants to use the dialpad toward the exact line, so un-hold the holding line.
339 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
340 if (call != null && call.getState() == State.ONHOLD) {
341 call.unhold();
342 }
343 }
344 }
345
346 /**
347 * Show a phone account selection dialog if there is a call waiting for phone account selection.
348 *
349 * @return true if the dialog was shown.
350 */
351 private boolean showPhoneAccountSelectionDialog() {
352 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
353 if (waitingForAccountCall == null) {
354 return false;
355 }
356
357 Bundle extras = waitingForAccountCall.getIntentExtras();
358 List<PhoneAccountHandle> phoneAccountHandles =
359 extras == null
360 ? new ArrayList<>()
361 : extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
362
363 selectPhoneAccountDialogFragment =
364 SelectPhoneAccountDialogFragment.newInstance(
365 R.string.select_phone_account_for_calls,
366 true /* canSetDefault */,
367 0 /* setDefaultResId */,
368 phoneAccountHandles,
369 selectPhoneAccountListener,
370 waitingForAccountCall.getId(),
371 null /* hints */);
372 selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
373 return true;
374 }
375
Eric Erfanianccca3152017-02-22 16:32:36 -0800376 @Override
377 protected void onSaveInstanceState(Bundle out) {
linyuh57b093b2017-11-17 14:32:32 -0800378 LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
379
380 // TODO: DialpadFragment should handle this as part of its own state
linyuhc3968e62017-11-20 17:40:50 -0800381 out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible());
linyuh57b093b2017-11-17 14:32:32 -0800382 DialpadFragment dialpadFragment = getDialpadFragment();
383 if (dialpadFragment != null) {
linyuhc3968e62017-11-20 17:40:50 -0800384 out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText());
linyuh57b093b2017-11-17 14:32:32 -0800385 }
386
linyuhc3968e62017-11-20 17:40:50 -0800387 out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
388 out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
389 out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
linyuh57b093b2017-11-17 14:32:32 -0800390
Eric Erfanianccca3152017-02-22 16:32:36 -0800391 super.onSaveInstanceState(out);
392 isVisible = false;
393 }
394
395 @Override
396 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700397 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800398 super.onStart();
linyuh57b093b2017-11-17 14:32:32 -0800399
Eric Erfanianccca3152017-02-22 16:32:36 -0800400 isVisible = true;
401 showMainInCallFragment();
linyuh57b093b2017-11-17 14:32:32 -0800402
403 InCallPresenter.getInstance().setActivity(this);
404 enableInCallOrientationEventListener(
405 getRequestedOrientation()
406 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
407 InCallPresenter.getInstance().onActivityStarted();
408
yueg10f6e822018-01-17 15:32:18 -0800409 if (!isRecreating) {
410 InCallPresenter.getInstance().onUiShowing(true);
411 }
412
Eric Erfanianccca3152017-02-22 16:32:36 -0800413 if (ActivityCompat.isInMultiWindowMode(this)
414 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
415 // Hide the dialpad because there may not be enough room
416 showDialpadFragment(false, false);
417 }
linyuh57b093b2017-11-17 14:32:32 -0800418
Eric Erfanian2ca43182017-08-31 06:57:16 -0700419 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800420 }
421
422 @Override
423 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700424 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 super.onResume();
linyuhc3968e62017-11-20 17:40:50 -0800426
427 if (!InCallPresenter.getInstance().isReadyForTearDown()) {
428 updateTaskDescription();
yueg10f6e822018-01-17 15:32:18 -0800429 InCallPresenter.getInstance().updateNotification();
linyuhc3968e62017-11-20 17:40:50 -0800430 }
431
432 // If there is a pending request to show or hide the dialpad, handle that now.
433 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
434 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
435 // Exit fullscreen so that the user has access to the dialpad hide/show button.
436 // This is important when showing the dialpad from within dialer.
437 InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */);
438
439 showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
440 animateDialpadOnShow = false;
441
442 DialpadFragment dialpadFragment = getDialpadFragment();
443 if (dialpadFragment != null) {
444 dialpadFragment.setDtmfText(dtmfTextToPrepopulate);
445 dtmfTextToPrepopulate = null;
446 }
447 } else {
448 LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad");
449 if (getDialpadFragment() != null) {
450 showDialpadFragment(false /* show */, false /* animate */);
451 }
452 }
453 showDialpadRequest = DIALPAD_REQUEST_NONE;
454 }
455 updateNavigationBar(isDialpadVisible());
456
457 if (showPostCharWaitDialogOnResume) {
458 showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
459 }
460
461 CallList.getInstance()
462 .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false));
463
Eric Erfanianccca3152017-02-22 16:32:36 -0800464 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
465 pseudoScreenState.addListener(this);
466 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700467 Trace.endSection();
weijiaxu650e7cc2017-10-31 12:38:54 -0700468 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
469 ThreadUtil.postDelayedOnUiThread(
weijiaxuc950a9b2017-11-06 16:39:04 -0800470 () ->
471 Logger.get(this)
472 .logRecordMemory(LoggingBindings.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME),
weijiaxu650e7cc2017-10-31 12:38:54 -0700473 1000);
Eric Erfanianccca3152017-02-22 16:32:36 -0800474 }
475
Eric Erfanianccca3152017-02-22 16:32:36 -0800476 @Override
477 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700478 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 super.onPause();
linyuh57b093b2017-11-17 14:32:32 -0800480
481 DialpadFragment dialpadFragment = getDialpadFragment();
482 if (dialpadFragment != null) {
483 dialpadFragment.onDialerKeyUp(null);
484 }
485
yueg10f6e822018-01-17 15:32:18 -0800486 InCallPresenter.getInstance().updateNotification();
linyuh57b093b2017-11-17 14:32:32 -0800487
Eric Erfanianccca3152017-02-22 16:32:36 -0800488 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700489 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800490 }
491
492 @Override
493 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700494 Trace.beginSection("InCallActivity.onStop");
wangqi4d705e52017-09-28 12:23:35 -0700495 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800496 super.onStop();
linyuh57b093b2017-11-17 14:32:32 -0800497
498 // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
499 // user presses the home button).
500 // Without this the pending call will get stuck on phone account selection and new calls can't
501 // be created.
502 // Skip this when the screen is locked since the activity may complete its current life cycle
503 // and restart.
linyuhc3968e62017-11-20 17:40:50 -0800504 if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
linyuh57b093b2017-11-17 14:32:32 -0800505 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
506 if (waitingForAccountCall != null) {
507 waitingForAccountCall.disconnect();
508 }
509 }
510
511 enableInCallOrientationEventListener(false);
512 InCallPresenter.getInstance().updateIsChangingConfigurations();
513 InCallPresenter.getInstance().onActivityStopped();
linyuhc3968e62017-11-20 17:40:50 -0800514 if (!isRecreating) {
yueg10f6e822018-01-17 15:32:18 -0800515 InCallPresenter.getInstance().onUiShowing(false);
linyuh57b093b2017-11-17 14:32:32 -0800516 if (errorDialog != null) {
517 errorDialog.dismiss();
518 }
519 }
520
yueg10f6e822018-01-17 15:32:18 -0800521 if (isFinishing()) {
522 InCallPresenter.getInstance().unsetActivity(this);
523 }
524
Eric Erfanian2ca43182017-08-31 06:57:16 -0700525 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800526 }
527
528 @Override
529 protected void onDestroy() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700530 Trace.beginSection("InCallActivity.onDestroy");
Eric Erfanianccca3152017-02-22 16:32:36 -0800531 super.onDestroy();
linyuh57b093b2017-11-17 14:32:32 -0800532
533 InCallPresenter.getInstance().unsetActivity(this);
534 InCallPresenter.getInstance().updateIsChangingConfigurations();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700535 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800536 }
537
538 @Override
539 public void finish() {
540 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700541 // When user select incall ui from recents after the call is disconnected, it tries to launch
542 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
543 // crash.
544 // By calling finishAndRemoveTask() instead of finish() the task associated with
545 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
546 // this case.
547 //
548 // Calling finish won't clear the task and normally when an activity finishes it shouldn't
549 // clear the task since there could be parent activity in the same task that's still alive.
550 // But InCallActivity is special since it's singleInstance which means it's root activity and
551 // only instance of activity in the task. So it should be safe to also remove task when
552 // finishing.
553 // It's also necessary in the sense of it's excluded from recents. So whenever the activity
554 // finishes, the task should also be removed since it doesn't make sense to go back to it in
555 // anyway anymore.
556 super.finishAndRemoveTask();
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 }
558 }
559
560 private boolean shouldCloseActivityOnFinish() {
linyuhc3968e62017-11-20 17:40:50 -0800561 if (!isVisible) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 LogUtil.i(
563 "InCallActivity.shouldCloseActivityOnFinish",
564 "allowing activity to be closed because it's not visible");
565 return true;
566 }
567
twyen8efb4952017-10-06 16:35:54 -0700568 if (InCallPresenter.getInstance().isInCallUiLocked()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800569 LogUtil.i(
570 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700571 "in call ui is locked, not closing activity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800572 return false;
573 }
574
575 LogUtil.i(
576 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700577 "activity is visible and has no locks, allowing activity to close");
Eric Erfanianccca3152017-02-22 16:32:36 -0800578 return true;
579 }
580
581 @Override
582 protected void onNewIntent(Intent intent) {
linyuhc3968e62017-11-20 17:40:50 -0800583 LogUtil.enterBlock("InCallActivity.onNewIntent");
Eric Erfanianccca3152017-02-22 16:32:36 -0800584
585 // If the screen is off, we need to make sure it gets turned on for incoming calls.
586 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
587 // when the activity is first created. Therefore, to ensure the screen is turned on
588 // for the call waiting case, we recreate() the current activity. There should be no jank from
589 // this since the screen is already off and will remain so until our new activity is up.
linyuhc3968e62017-11-20 17:40:50 -0800590 if (!isVisible) {
591 onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800592 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
593 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700594 } else {
linyuhc3968e62017-11-20 17:40:50 -0800595 onNewIntent(intent, false /* isRecreating */);
596 }
597 }
598
yuega3305352018-01-09 11:02:47 -0800599 @VisibleForTesting
600 void onNewIntent(Intent intent, boolean isRecreating) {
linyuhc3968e62017-11-20 17:40:50 -0800601 this.isRecreating = isRecreating;
602
603 // We're being re-launched with a new Intent. Since it's possible for a single InCallActivity
604 // instance to persist indefinitely (even if we finish() ourselves), this sequence can
605 // happen any time the InCallActivity needs to be displayed.
606
607 // Stash away the new intent so that we can get it in the future by calling getIntent().
608 // Otherwise getIntent() will return the original Intent from when we first got created.
609 setIntent(intent);
610
611 // Activities are always paused before receiving a new intent, so we can count on our onResume()
612 // method being called next.
613
614 // Just like in onCreate(), handle the intent.
615 // Skip if InCallActivity is going to be recreated since this will be called in onCreate().
616 if (!isRecreating) {
617 internalResolveIntent(intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800618 }
619 }
620
621 @Override
622 public void onBackPressed() {
linyuh57b093b2017-11-17 14:32:32 -0800623 LogUtil.enterBlock("InCallActivity.onBackPressed");
624
linyuhc3968e62017-11-20 17:40:50 -0800625 if (!isVisible) {
linyuh57b093b2017-11-17 14:32:32 -0800626 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800627 }
linyuh57b093b2017-11-17 14:32:32 -0800628
629 if (!getCallCardFragmentVisible()) {
630 return;
631 }
632
633 DialpadFragment dialpadFragment = getDialpadFragment();
634 if (dialpadFragment != null && dialpadFragment.isVisible()) {
635 showDialpadFragment(false /* show */, true /* animate */);
636 return;
637 }
638
639 if (CallList.getInstance().getIncomingCall() != null) {
640 LogUtil.i(
641 "InCallActivity.onBackPressed",
642 "Ignore the press of the back key when an incoming call is ringing");
643 return;
644 }
645
646 // Nothing special to do. Fall back to the default behavior.
647 super.onBackPressed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800648 }
649
650 @Override
651 public boolean onOptionsItemSelected(MenuItem item) {
652 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
653 if (item.getItemId() == android.R.id.home) {
654 onBackPressed();
655 return true;
656 }
657 return super.onOptionsItemSelected(item);
658 }
659
660 @Override
661 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800662 DialpadFragment dialpadFragment = getDialpadFragment();
663 if (dialpadFragment != null
664 && dialpadFragment.isVisible()
665 && dialpadFragment.onDialerKeyUp(event)) {
666 return true;
667 }
668
669 if (keyCode == KeyEvent.KEYCODE_CALL) {
670 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
671 return true;
672 }
673
674 return super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800675 }
676
677 @Override
678 public boolean onKeyDown(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800679 switch (keyCode) {
680 case KeyEvent.KEYCODE_CALL:
681 if (!InCallPresenter.getInstance().handleCallKey()) {
682 LogUtil.e(
683 "InCallActivity.onKeyDown",
684 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
685 }
686 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
687 return true;
688
689 // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
690 // is exactly what's needed, namely
691 // (1) "hang up" if there's an active call, or
692 // (2) "don't answer" if there's an incoming call.
693 // (See PhoneWindowManager for implementation details.)
694
695 case KeyEvent.KEYCODE_CAMERA:
696 // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
697 return true;
698
699 case KeyEvent.KEYCODE_VOLUME_UP:
700 case KeyEvent.KEYCODE_VOLUME_DOWN:
701 case KeyEvent.KEYCODE_VOLUME_MUTE:
702 // Ringer silencing handled by PhoneWindowManager.
703 break;
704
705 case KeyEvent.KEYCODE_MUTE:
706 TelecomAdapter.getInstance()
707 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
708 return true;
709
710 case KeyEvent.KEYCODE_SLASH:
711 // When verbose logging is enabled, dump the view for debugging/testing purposes.
712 if (LogUtil.isVerboseEnabled()) {
713 View decorView = getWindow().getDecorView();
714 LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
715 return true;
716 }
717 break;
718
719 case KeyEvent.KEYCODE_EQUALS:
720 break;
721
722 default: // fall out
723 }
724
725 // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
726 // in DTMF (Dual-tone multi-frequency signaling) code.
727 DialpadFragment dialpadFragment = getDialpadFragment();
728 if (dialpadFragment != null
729 && dialpadFragment.isVisible()
730 && dialpadFragment.onDialerKeyDown(event)) {
731 return true;
732 }
733
734 return super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800735 }
736
737 public boolean isInCallScreenAnimating() {
738 return false;
739 }
740
741 public void showConferenceFragment(boolean show) {
742 if (show) {
743 startActivity(new Intent(this, ManageConferenceActivity.class));
744 }
745 }
746
linyuhc3968e62017-11-20 17:40:50 -0800747 public void showDialpadFragment(boolean show, boolean animate) {
748 if (show == isDialpadVisible()) {
749 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 }
linyuhc3968e62017-11-20 17:40:50 -0800751
752 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
753 if (dialpadFragmentManager == null) {
754 LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager");
755 return;
756 }
757
758 if (!animate) {
759 if (show) {
760 showDialpadFragment();
761 } else {
762 hideDialpadFragment();
763 }
764 } else {
765 if (show) {
766 showDialpadFragment();
767 getDialpadFragment().animateShowDialpad();
768 }
769 getDialpadFragment()
770 .getView()
771 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
772 }
773
774 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
775 if (sensor != null) {
776 sensor.onDialpadVisible(show);
777 }
778 showDialpadRequest = DIALPAD_REQUEST_NONE;
779
780 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
781 // repositions itself.
782 getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
783 }
784
785 private void showDialpadFragment() {
786 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
787 if (dialpadFragmentManager == null) {
788 return;
789 }
790
791 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
792 DialpadFragment dialpadFragment = getDialpadFragment();
793 if (dialpadFragment == null) {
794 transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT);
795 } else {
796 transaction.show(dialpadFragment);
calderwoodrad5883872017-12-12 15:29:12 -0800797 dialpadFragment.setUserVisibleHint(true);
linyuhc3968e62017-11-20 17:40:50 -0800798 }
799 transaction.commitAllowingStateLoss();
800 dialpadFragmentManager.executePendingTransactions();
801
802 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this);
803 updateNavigationBar(true /* isDialpadVisible */);
804 }
805
806 private void hideDialpadFragment() {
807 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
808 if (dialpadFragmentManager == null) {
809 return;
810 }
811
calderwoodrad5883872017-12-12 15:29:12 -0800812 DialpadFragment dialpadFragment = getDialpadFragment();
linyuhc3968e62017-11-20 17:40:50 -0800813 if (dialpadFragment != null) {
814 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
815 transaction.hide(dialpadFragment);
816 transaction.commitAllowingStateLoss();
817 dialpadFragmentManager.executePendingTransactions();
calderwoodrad5883872017-12-12 15:29:12 -0800818 dialpadFragment.setUserVisibleHint(false);
linyuhc3968e62017-11-20 17:40:50 -0800819 }
820 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 }
822
823 public boolean isDialpadVisible() {
linyuh69a25062017-11-15 16:18:51 -0800824 DialpadFragment dialpadFragment = getDialpadFragment();
calderwoodraa584bcd2018-01-24 12:19:56 -0800825 return dialpadFragment != null
826 && dialpadFragment.isAdded()
827 && !dialpadFragment.isHidden()
828 && dialpadFragment.getView() != null
829 && dialpadFragment.getUserVisibleHint();
linyuh69a25062017-11-15 16:18:51 -0800830 }
831
linyuhc3968e62017-11-20 17:40:50 -0800832 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
linyuh69a25062017-11-15 16:18:51 -0800833 @Nullable
linyuhc3968e62017-11-20 17:40:50 -0800834 private DialpadFragment getDialpadFragment() {
linyuh69a25062017-11-15 16:18:51 -0800835 FragmentManager fragmentManager = getDialpadFragmentManager();
836 if (fragmentManager == null) {
837 return null;
838 }
linyuhc3968e62017-11-20 17:40:50 -0800839 return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800840 }
841
842 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh57b093b2017-11-17 14:32:32 -0800843 updateTaskDescription();
844
845 if (newForegroundCall == null || !didShowAnswerScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800846 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
linyuh57b093b2017-11-17 14:32:32 -0800847 updateWindowBackgroundColor(0 /* progress */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800848 }
849 }
850
linyuhc3968e62017-11-20 17:40:50 -0800851 private void updateTaskDescription() {
linyuh57b093b2017-11-17 14:32:32 -0800852 int color =
853 getResources().getBoolean(R.bool.is_layout_landscape)
854 ? ResourcesCompat.getColor(
855 getResources(), R.color.statusbar_background_color, getTheme())
856 : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
857 setTaskDescription(
858 new TaskDescription(
859 getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
860 }
861
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
863 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
864 @ColorInt int top;
865 @ColorInt int middle;
866 @ColorInt int bottom;
867 @ColorInt int gray = 0x66000000;
868
869 if (ActivityCompat.isInMultiWindowMode(this)) {
870 top = themeColorManager.getBackgroundColorSolid();
871 middle = themeColorManager.getBackgroundColorSolid();
872 bottom = themeColorManager.getBackgroundColorSolid();
873 } else {
874 top = themeColorManager.getBackgroundColorTop();
875 middle = themeColorManager.getBackgroundColorMiddle();
876 bottom = themeColorManager.getBackgroundColorBottom();
877 }
878
879 if (progress < 0) {
880 float correctedProgress = Math.abs(progress);
881 top = ColorUtils.blendARGB(top, gray, correctedProgress);
882 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
883 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
884 }
885
886 boolean backgroundDirty = false;
887 if (backgroundDrawable == null) {
888 backgroundDrawableColors = new int[] {top, middle, bottom};
889 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
890 backgroundDirty = true;
891 } else {
892 if (backgroundDrawableColors[0] != top) {
893 backgroundDrawableColors[0] = top;
894 backgroundDirty = true;
895 }
896 if (backgroundDrawableColors[1] != middle) {
897 backgroundDrawableColors[1] = middle;
898 backgroundDirty = true;
899 }
900 if (backgroundDrawableColors[2] != bottom) {
901 backgroundDrawableColors[2] = bottom;
902 backgroundDirty = true;
903 }
904 if (backgroundDirty) {
905 backgroundDrawable.setColors(backgroundDrawableColors);
906 }
907 }
908
909 if (backgroundDirty) {
910 getWindow().setBackgroundDrawable(backgroundDrawable);
911 }
912 }
913
914 public boolean isVisible() {
915 return isVisible;
916 }
917
918 public boolean getCallCardFragmentVisible() {
919 return didShowInCallScreen || didShowVideoCallScreen;
920 }
921
922 public void dismissKeyguard(boolean dismiss) {
linyuh9c327da2017-11-14 12:33:48 -0800923 if (dismissKeyguard == dismiss) {
924 return;
925 }
926
927 dismissKeyguard = dismiss;
928 if (dismiss) {
929 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
930 } else {
931 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
932 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800933 }
934
linyuhc3968e62017-11-20 17:40:50 -0800935 public void showDialogForPostCharWait(String callId, String chars) {
936 if (isVisible) {
937 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
938 fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT);
939
940 showPostCharWaitDialogOnResume = false;
941 showPostCharWaitDialogCallId = null;
942 showPostCharWaitDialogChars = null;
943 } else {
944 showPostCharWaitDialogOnResume = true;
945 showPostCharWaitDialogCallId = callId;
946 showPostCharWaitDialogChars = chars;
947 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800948 }
949
linyuh7b86f562017-11-16 11:24:09 -0800950 public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
951 LogUtil.i(
952 "InCallActivity.showDialogOrToastForDisconnectedCall",
953 "disconnect cause: %s",
954 disconnectMessage);
955
956 if (disconnectMessage.dialog == null || isFinishing()) {
957 return;
958 }
959
960 dismissPendingDialogs();
961
962 // Show a toast if the app is in background when a dialog can't be visible.
963 if (!isVisible()) {
964 Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
965 .show();
966 return;
967 }
968
969 // Show the dialog.
linyuhc3968e62017-11-20 17:40:50 -0800970 errorDialog = disconnectMessage.dialog;
linyuh7b86f562017-11-16 11:24:09 -0800971 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
972 disconnectMessage.dialog.setOnDismissListener(
973 dialogInterface -> {
974 lock.release();
975 onDialogDismissed();
976 });
977 disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
978 disconnectMessage.dialog.show();
979 }
980
981 private void onDialogDismissed() {
linyuhc3968e62017-11-20 17:40:50 -0800982 errorDialog = null;
linyuh7b86f562017-11-16 11:24:09 -0800983 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800984 }
985
986 public void dismissPendingDialogs() {
linyuhc3968e62017-11-20 17:40:50 -0800987 LogUtil.enterBlock("InCallActivity.dismissPendingDialogs");
linyuhf99f6302017-11-15 11:23:51 -0800988
989 if (!isVisible) {
990 // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
991 // been called.
Eric Erfanianccca3152017-02-22 16:32:36 -0800992 LogUtil.i(
993 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
994 needDismissPendingDialogs = true;
linyuhf99f6302017-11-15 11:23:51 -0800995 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800996 }
linyuhf99f6302017-11-15 11:23:51 -0800997
998 // Dismiss the error dialog
linyuhf99f6302017-11-15 11:23:51 -0800999 if (errorDialog != null) {
1000 errorDialog.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001001 errorDialog = null;
linyuhf99f6302017-11-15 11:23:51 -08001002 }
1003
1004 // Dismiss the phone account selection dialog
linyuhf99f6302017-11-15 11:23:51 -08001005 if (selectPhoneAccountDialogFragment != null) {
1006 selectPhoneAccountDialogFragment.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001007 selectPhoneAccountDialogFragment = null;
linyuhf99f6302017-11-15 11:23:51 -08001008 }
1009
1010 // Dismiss the dialog for international call on WiFi
1011 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
1012 (InternationalCallOnWifiDialogFragment)
linyuhc3968e62017-11-20 17:40:50 -08001013 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
linyuhf99f6302017-11-15 11:23:51 -08001014 if (internationalCallOnWifiFragment != null) {
1015 internationalCallOnWifiFragment.dismiss();
1016 }
1017
1018 // Dismiss the answer screen
1019 AnswerScreen answerScreen = getAnswerScreen();
1020 if (answerScreen != null) {
1021 answerScreen.dismissPendingDialogs();
1022 }
1023
1024 needDismissPendingDialogs = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001025 }
1026
linyuhc3968e62017-11-20 17:40:50 -08001027 private void enableInCallOrientationEventListener(boolean enable) {
linyuh69a25062017-11-15 16:18:51 -08001028 if (enable) {
1029 inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
1030 } else {
1031 inCallOrientationEventListener.disable();
1032 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001033 }
1034
1035 public void setExcludeFromRecents(boolean exclude) {
linyuhc3968e62017-11-20 17:40:50 -08001036 int taskId = getTaskId();
1037
1038 List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks();
1039 for (AppTask task : tasks) {
1040 try {
1041 if (task.getTaskInfo().id == taskId) {
1042 task.setExcludeFromRecents(exclude);
1043 }
1044 } catch (RuntimeException e) {
1045 LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e);
1046 }
1047 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001048 }
1049
Eric Erfanianccca3152017-02-22 16:32:36 -08001050 @Nullable
1051 public FragmentManager getDialpadFragmentManager() {
1052 InCallScreen inCallScreen = getInCallScreen();
1053 if (inCallScreen != null) {
1054 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
1055 }
1056 return null;
1057 }
1058
1059 public int getDialpadContainerId() {
1060 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
1061 }
1062
1063 @Override
1064 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
1065 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
1066 if (call == null) {
1067 // This is a work around for a bug where we attempt to create a new delegate after the call
1068 // has already been removed. An example of when this can happen is:
1069 // 1. incoming video call in landscape mode
1070 // 2. remote party hangs up
1071 // 3. activity switches from landscape to portrait
1072 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
1073 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
1074 // because this new state is transient and the activity will be destroyed soon.
1075 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
1076 return new AnswerScreenPresenterStub();
1077 } else {
1078 return new AnswerScreenPresenter(
1079 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
1080 }
1081 }
1082
1083 @Override
1084 public InCallScreenDelegate newInCallScreenDelegate() {
1085 return new CallCardPresenter(this);
1086 }
1087
1088 @Override
1089 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
1090 return new CallButtonPresenter(this);
1091 }
1092
1093 @Override
Eric Erfanian90508232017-03-24 09:31:16 -07001094 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
1095 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
1096 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
1097 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
1098 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001099 return new VideoCallPresenter();
1100 }
1101
1102 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001103 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -08001104 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001105 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001106 }
1107
linyuh7b86f562017-11-16 11:24:09 -08001108 public void showToastForWiFiToLteHandover(DialerCall call) {
1109 if (call.hasShownWiFiToLteHandoverToast()) {
1110 return;
1111 }
1112
1113 Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
1114 call.setHasShownWiFiToLteHandoverToast();
Eric Erfanianccca3152017-02-22 16:32:36 -08001115 }
1116
linyuh7b86f562017-11-16 11:24:09 -08001117 public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
1118 if (call.showWifiHandoverAlertAsToast()) {
1119 Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
1120 .show();
1121 return;
1122 }
1123
1124 dismissPendingDialogs();
1125
1126 AlertDialog.Builder builder =
1127 new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
1128
1129 // This allows us to use the theme of the dialog instead of the activity
1130 View dialogCheckBoxView =
1131 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
1132 CheckBox wifiHandoverFailureCheckbox =
1133 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
1134 wifiHandoverFailureCheckbox.setChecked(false);
1135
1136 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
linyuhc3968e62017-11-20 17:40:50 -08001137 errorDialog =
linyuh7b86f562017-11-16 11:24:09 -08001138 builder
1139 .setView(dialogCheckBoxView)
1140 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
1141 .setOnCancelListener(dialogInterface -> onDialogDismissed())
1142 .setPositiveButton(
1143 android.R.string.ok,
1144 (dialogInterface, id) -> {
1145 call.setDoNotShowDialogForHandoffToWifiFailure(
1146 wifiHandoverFailureCheckbox.isChecked());
1147 dialogInterface.cancel();
1148 onDialogDismissed();
1149 })
1150 .setOnDismissListener(dialogInterface -> lock.release())
1151 .create();
linyuh7b86f562017-11-16 11:24:09 -08001152 errorDialog.show();
Eric Erfanianccca3152017-02-22 16:32:36 -08001153 }
1154
linyuh7b86f562017-11-16 11:24:09 -08001155 public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
1156 if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
1157 LogUtil.i(
1158 "InCallActivity.showDialogForInternationalCallOnWifi",
1159 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
1160 return;
1161 }
1162
1163 InternationalCallOnWifiDialogFragment fragment =
1164 InternationalCallOnWifiDialogFragment.newInstance(
linyuhc3968e62017-11-20 17:40:50 -08001165 call.getId(), internationalCallOnWifiCallback);
1166 fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI);
Eric Erfanianc857f902017-05-15 14:05:33 -07001167 }
1168
Eric Erfanian938468d2017-10-24 14:05:52 -07001169 @Override
1170 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
1171 super.onMultiWindowModeChanged(isInMultiWindowMode);
linyuh57b093b2017-11-17 14:32:32 -08001172 updateNavigationBar(isDialpadVisible());
1173 }
1174
linyuhc3968e62017-11-20 17:40:50 -08001175 private void updateNavigationBar(boolean isDialpadVisible) {
linyuh57b093b2017-11-17 14:32:32 -08001176 if (ActivityCompat.isInMultiWindowMode(this)) {
1177 return;
1178 }
1179
1180 View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
1181 if (navigationBarBackground != null) {
1182 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
Eric Erfanian938468d2017-10-24 14:05:52 -07001183 }
1184 }
1185
Eric Erfanianccca3152017-02-22 16:32:36 -08001186 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -07001187 if (this.allowOrientationChange == allowOrientationChange) {
1188 return;
1189 }
1190 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001191 if (!allowOrientationChange) {
1192 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
1193 } else {
1194 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
1195 }
1196 enableInCallOrientationEventListener(allowOrientationChange);
1197 }
1198
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001199 public void hideMainInCallFragment() {
linyuhc3968e62017-11-20 17:40:50 -08001200 LogUtil.enterBlock("InCallActivity.hideMainInCallFragment");
1201 if (getCallCardFragmentVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001202 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1203 hideInCallScreenFragment(transaction);
1204 hideVideoCallScreenFragment(transaction);
1205 transaction.commitAllowingStateLoss();
1206 getSupportFragmentManager().executePendingTransactions();
1207 }
1208 }
1209
1210 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001211 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -08001212 // If the activity's onStart method hasn't been called yet then defer doing any work.
1213 if (!isVisible) {
1214 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001215 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001216 return;
1217 }
1218
1219 // Don't let this be reentrant.
1220 if (isInShowMainInCallFragment) {
1221 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001222 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001223 return;
1224 }
1225
1226 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001227 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
1228 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
wangqi219b8702018-02-13 09:34:41 -08001229 ShouldShowUiResult shouldShowRttUi = getShouldShowRttUi();
Eric Erfanianccca3152017-02-22 16:32:36 -08001230 LogUtil.i(
1231 "InCallActivity.showMainInCallFragment",
wangqi219b8702018-02-13 09:34:41 -08001232 "shouldShowAnswerUi: %b, shouldShowRttUi: %b, shouldShowVideoUi: %b "
1233 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowRttCallScreen: %b, "
1234 + "didShowVideoCallScreen: %b",
Eric Erfanianccca3152017-02-22 16:32:36 -08001235 shouldShowAnswerUi.shouldShow,
wangqi219b8702018-02-13 09:34:41 -08001236 shouldShowRttUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -07001237 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -08001238 didShowAnswerScreen,
1239 didShowInCallScreen,
wangqi219b8702018-02-13 09:34:41 -08001240 didShowRttCallScreen,
Eric Erfanianccca3152017-02-22 16:32:36 -08001241 didShowVideoCallScreen);
1242 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001243 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -08001244
1245 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
wangqi219b8702018-02-13 09:34:41 -08001246 boolean didChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001247 if (shouldShowAnswerUi.shouldShow) {
wangqi219b8702018-02-13 09:34:41 -08001248 didChange = hideInCallScreenFragment(transaction);
1249 didChange |= hideVideoCallScreenFragment(transaction);
1250 didChange |= hideRttCallScreenFragment(transaction);
1251 didChange |= showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001252 } else if (shouldShowVideoUi.shouldShow) {
wangqi219b8702018-02-13 09:34:41 -08001253 didChange = hideInCallScreenFragment(transaction);
1254 didChange |= showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
1255 didChange |= hideRttCallScreenFragment(transaction);
1256 didChange |= hideAnswerScreenFragment(transaction);
1257 } else if (shouldShowRttUi.shouldShow) {
1258 didChange = hideInCallScreenFragment(transaction);
1259 didChange |= hideVideoCallScreenFragment(transaction);
1260 didChange |= hideAnswerScreenFragment(transaction);
1261 didChange |= showRttCallScreenFragment(transaction, shouldShowRttUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001262 } else {
wangqi219b8702018-02-13 09:34:41 -08001263 didChange = showInCallScreenFragment(transaction);
1264 didChange |= hideVideoCallScreenFragment(transaction);
1265 didChange |= hideRttCallScreenFragment(transaction);
1266 didChange |= hideAnswerScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001267 }
1268
wangqi219b8702018-02-13 09:34:41 -08001269 if (didChange) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001270 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -08001271 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001272 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001273 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1274 }
1275 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001276 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001277 }
1278
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001279 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001280 DialerCall call = CallList.getInstance().getIncomingCall();
1281 if (call != null) {
1282 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001283 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001284 }
1285
1286 call = CallList.getInstance().getVideoUpgradeRequestCall();
1287 if (call != null) {
1288 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001289 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001290 }
1291
1292 // Check if we're showing the answer screen and the call is disconnected. If this condition is
1293 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
1294 // the user rejects an incoming call.
1295 call = CallList.getInstance().getFirstCall();
1296 if (call == null) {
1297 call = CallList.getInstance().getBackgroundCall();
1298 }
1299 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
1300 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001301 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001302 }
1303
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001304 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001305 }
1306
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001307 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001308 DialerCall call = CallList.getInstance().getFirstCall();
1309 if (call == null) {
1310 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001311 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001312 }
1313
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001314 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001315 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001316 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001317 }
1318
linyuh8fbecce2017-12-18 13:53:09 -08001319 if (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001320 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001321 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001322 }
1323
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001324 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001325 }
1326
wangqi219b8702018-02-13 09:34:41 -08001327 private static ShouldShowUiResult getShouldShowRttUi() {
1328 DialerCall call = CallList.getInstance().getFirstCall();
1329 if (call == null) {
1330 LogUtil.i("InCallActivity.getShouldShowRttUi", "null call");
1331 return new ShouldShowUiResult(false, null);
1332 }
1333
1334 if (call.isRttCall()) {
1335 LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call");
1336 return new ShouldShowUiResult(true, call);
1337 }
1338
1339 if (call.hasSentRttUpgradeRequest()) {
1340 LogUtil.i("InCallActivity.getShouldShowRttUi", "upgrading to rtt");
1341 return new ShouldShowUiResult(true, call);
1342 }
1343
1344 return new ShouldShowUiResult(false, null);
1345 }
1346
Eric Erfanianccca3152017-02-22 16:32:36 -08001347 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
1348 // When rejecting a call the active call can become null in which case we should continue
1349 // showing the answer screen.
1350 if (didShowAnswerScreen && call == null) {
1351 return false;
1352 }
1353
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001354 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
1355
1356 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -08001357
1358 // Check if we're already showing an answer screen for this call.
1359 if (didShowAnswerScreen) {
1360 AnswerScreen answerScreen = getAnswerScreen();
1361 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001362 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -07001363 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
1364 && !answerScreen.isActionTimeout()) {
1365 LogUtil.d(
1366 "InCallActivity.showAnswerScreenFragment",
1367 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -08001368 return false;
1369 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001370 if (answerScreen.isActionTimeout()) {
1371 LogUtil.i(
1372 "InCallActivity.showAnswerScreenFragment",
1373 "answer fragment exists but has been accepted/rejected and timed out");
1374 } else {
1375 LogUtil.i(
1376 "InCallActivity.showAnswerScreenFragment",
1377 "answer fragment exists but arguments do not match");
1378 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001379 hideAnswerScreenFragment(transaction);
1380 }
1381
1382 // Show a new answer screen.
1383 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -07001384 AnswerBindings.createAnswerScreen(
1385 call.getId(),
wangqi219b8702018-02-13 09:34:41 -08001386 call.isRttCall(),
Eric Erfanianfc37b022017-03-21 10:11:17 -07001387 call.isVideoCall(),
1388 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -07001389 call.getVideoTech().isSelfManagedCamera(),
1390 shouldAllowAnswerAndRelease(call),
1391 CallList.getInstance().getBackgroundCall() != null);
linyuhc3968e62017-11-20 17:40:50 -08001392 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001393
1394 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
1395 didShowAnswerScreen = true;
1396 return true;
1397 }
1398
Eric Erfanian90508232017-03-24 09:31:16 -07001399 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
1400 if (CallList.getInstance().getActiveCall() == null) {
1401 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
1402 return false;
1403 }
1404 if (getSystemService(TelephonyManager.class).getPhoneType()
1405 == TelephonyManager.PHONE_TYPE_CDMA) {
1406 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
1407 return false;
1408 }
1409 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
1410 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
1411 return false;
1412 }
linyuhc3968e62017-11-20 17:40:50 -08001413 if (!ConfigProviderBindings.get(this)
1414 .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) {
Eric Erfanian90508232017-03-24 09:31:16 -07001415 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
1416 return false;
1417 }
1418
1419 return true;
1420 }
1421
Eric Erfanianccca3152017-02-22 16:32:36 -08001422 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
1423 if (!didShowAnswerScreen) {
1424 return false;
1425 }
1426 AnswerScreen answerScreen = getAnswerScreen();
1427 if (answerScreen != null) {
1428 transaction.remove(answerScreen.getAnswerScreenFragment());
1429 }
1430
1431 didShowAnswerScreen = false;
1432 return true;
1433 }
1434
1435 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
1436 if (didShowInCallScreen) {
1437 return false;
1438 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001439 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
linyuhc3968e62017-11-20 17:40:50 -08001440 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001441 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1442 didShowInCallScreen = true;
1443 return true;
1444 }
1445
1446 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
1447 if (!didShowInCallScreen) {
1448 return false;
1449 }
1450 InCallScreen inCallScreen = getInCallScreen();
1451 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001452 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -08001453 }
1454 didShowInCallScreen = false;
1455 return true;
1456 }
1457
wangqi219b8702018-02-13 09:34:41 -08001458 private boolean showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
1459 if (didShowRttCallScreen) {
1460 // This shouldn't happen since only one RTT call is allow at same time.
1461 if (!getRttCallScreen().getCallId().equals(call.getId())) {
1462 LogUtil.e("InCallActivity.showRttCallScreenFragment", "RTT call id doesn't match");
1463 }
1464 return false;
1465 }
1466 RttCallScreen rttCallScreen = RttBindings.createRttCallScreen(call.getId());
1467 transaction.add(R.id.main, rttCallScreen.getRttCallScreenFragment(), Tags.RTT_CALL_SCREEN);
1468 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1469 didShowRttCallScreen = true;
1470 return true;
1471 }
1472
1473 private boolean hideRttCallScreenFragment(FragmentTransaction transaction) {
1474 if (!didShowRttCallScreen) {
1475 return false;
1476 }
1477 RttCallScreen rttCallScreen = getRttCallScreen();
1478 if (rttCallScreen != null) {
1479 transaction.remove(rttCallScreen.getRttCallScreenFragment());
1480 }
1481 didShowRttCallScreen = false;
1482 return true;
1483 }
1484
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001485 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001486 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001487 VideoCallScreen videoCallScreen = getVideoCallScreen();
1488 if (videoCallScreen.getCallId().equals(call.getId())) {
1489 return false;
1490 }
1491 LogUtil.i(
1492 "InCallActivity.showVideoCallScreenFragment",
1493 "video call fragment exists but arguments do not match");
1494 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001495 }
1496
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001497 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
1498
Eric Erfanian90508232017-03-24 09:31:16 -07001499 VideoCallScreen videoCallScreen =
1500 VideoBindings.createVideoCallScreen(
1501 call.getId(), call.getVideoTech().shouldUseSurfaceView());
linyuhc3968e62017-11-20 17:40:50 -08001502 transaction.add(
1503 R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001504
1505 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1506 didShowVideoCallScreen = true;
1507 return true;
1508 }
1509
1510 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
1511 if (!didShowVideoCallScreen) {
1512 return false;
1513 }
1514 VideoCallScreen videoCallScreen = getVideoCallScreen();
1515 if (videoCallScreen != null) {
1516 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
1517 }
1518 didShowVideoCallScreen = false;
1519 return true;
1520 }
1521
linyuhc3968e62017-11-20 17:40:50 -08001522 private AnswerScreen getAnswerScreen() {
1523 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001524 }
1525
linyuhc3968e62017-11-20 17:40:50 -08001526 private InCallScreen getInCallScreen() {
1527 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001528 }
1529
linyuhc3968e62017-11-20 17:40:50 -08001530 private VideoCallScreen getVideoCallScreen() {
1531 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001532 }
1533
wangqi219b8702018-02-13 09:34:41 -08001534 private RttCallScreen getRttCallScreen() {
1535 return (RttCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.RTT_CALL_SCREEN);
1536 }
1537
Eric Erfanianccca3152017-02-22 16:32:36 -08001538 @Override
1539 public void onPseudoScreenStateChanged(boolean isOn) {
1540 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
1541 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
1542 }
1543
1544 /**
1545 * For some touch related issue, turning off the screen can be faked by drawing a black view over
1546 * the activity. All touch events started when the screen is "off" is rejected.
1547 *
1548 * @see PseudoScreenState
1549 */
1550 @Override
1551 public boolean dispatchTouchEvent(MotionEvent event) {
1552 // Reject any gesture that started when the screen is in the fake off state.
1553 if (touchDownWhenPseudoScreenOff) {
1554 if (event.getAction() == MotionEvent.ACTION_UP) {
1555 touchDownWhenPseudoScreenOff = false;
1556 }
1557 return true;
1558 }
1559 // Reject all touch event when the screen is in the fake off state.
1560 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
1561 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1562 touchDownWhenPseudoScreenOff = true;
1563 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
1564 }
1565 return true;
1566 }
1567 return super.dispatchTouchEvent(event);
1568 }
1569
wangqi219b8702018-02-13 09:34:41 -08001570 @Override
1571 public RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen videoCallScreen) {
1572 return new RttCallPresenter();
1573 }
1574
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001575 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -08001576 public final boolean shouldShow;
1577 public final DialerCall call;
1578
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001579 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001580 this.shouldShow = shouldShow;
1581 this.call = call;
1582 }
1583 }
linyuhc3968e62017-11-20 17:40:50 -08001584
1585 private static final class IntentExtraNames {
1586 static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent";
1587 static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
1588 static final String SHOW_DIALPAD = "InCallActivity.show_dialpad";
1589 }
1590
1591 private static final class KeysForSavedInstance {
1592 static final String DIALPAD_TEXT = "InCallActivity.dialpad_text";
1593 static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
1594 static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
1595 static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
1596 }
1597
1598 /** Request codes for pending intents. */
1599 public static final class PendingIntentRequestCodes {
1600 static final int NON_FULL_SCREEN = 0;
1601 static final int FULL_SCREEN = 1;
1602 static final int BUBBLE = 2;
1603 }
1604
1605 private static final class Tags {
1606 static final String ANSWER_SCREEN = "tag_answer_screen";
1607 static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment";
1608 static final String IN_CALL_SCREEN = "tag_in_call_screen";
1609 static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
1610 static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
1611 static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
wangqi219b8702018-02-13 09:34:41 -08001612 static final String RTT_CALL_SCREEN = "tag_rtt_call_screen";
linyuhc3968e62017-11-20 17:40:50 -08001613 static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
1614 }
1615
1616 private static final class ConfigNames {
1617 static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
1618 }
1619
1620 private static final class InternationalCallOnWifiCallback
1621 implements InternationalCallOnWifiDialogFragment.Callback {
1622 private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName();
1623
1624 @Override
1625 public void continueCall(@NonNull String callId) {
1626 LogUtil.i(TAG, "Continuing call with ID: %s", callId);
1627 }
1628
1629 @Override
1630 public void cancelCall(@NonNull String callId) {
1631 DialerCall call = CallList.getInstance().getCallById(callId);
1632 if (call == null) {
1633 LogUtil.i(TAG, "Call destroyed before the dialog is closed");
1634 return;
1635 }
1636
1637 LogUtil.i(TAG, "Disconnecting international call on WiFi");
1638 call.disconnect();
1639 }
1640 }
1641
1642 private static final class SelectPhoneAccountListener
1643 extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener {
1644 private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName();
1645
1646 @Override
1647 public void onPhoneAccountSelected(
1648 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
1649 DialerCall call = CallList.getInstance().getCallById(callId);
1650 LogUtil.i(TAG, "Phone account select with call:\n%s", call);
1651
1652 if (call != null) {
1653 call.phoneAccountSelected(selectedAccountHandle, setDefault);
1654 }
1655 }
1656
1657 @Override
1658 public void onDialogDismissed(String callId) {
1659 DialerCall call = CallList.getInstance().getCallById(callId);
1660 LogUtil.i(TAG, "Disconnecting call:\n%s" + call);
1661
1662 if (call != null) {
1663 call.disconnect();
1664 }
1665 }
1666 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001667}