blob: 6f2ad8bf33ee617a9d0e5cb5103dc399e054de1a [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;
twyen73a74c32018-03-07 12:12:24 -080042import android.telecom.Call;
linyuhc3968e62017-11-20 17:40:50 -080043import android.telecom.CallAudioState;
44import android.telecom.PhoneAccountHandle;
Eric Erfanian90508232017-03-24 09:31:16 -070045import android.telephony.TelephonyManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080046import android.view.KeyEvent;
47import android.view.MenuItem;
48import android.view.MotionEvent;
49import android.view.View;
linyuh9c327da2017-11-14 12:33:48 -080050import android.view.WindowManager;
linyuhc3968e62017-11-20 17:40:50 -080051import android.view.animation.Animation;
52import android.view.animation.AnimationUtils;
linyuh7b86f562017-11-16 11:24:09 -080053import android.widget.CheckBox;
54import android.widget.Toast;
linyuhf99f6302017-11-15 11:23:51 -080055import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
linyuhc3968e62017-11-20 17:40:50 -080056import com.android.dialer.animation.AnimUtils;
57import com.android.dialer.animation.AnimationListenerAdapter;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070058import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080059import com.android.dialer.common.LogUtil;
twyen73a74c32018-03-07 12:12:24 -080060import com.android.dialer.common.concurrent.DialerExecutorComponent;
weijiaxu650e7cc2017-10-31 12:38:54 -070061import com.android.dialer.common.concurrent.ThreadUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080062import com.android.dialer.compat.ActivityCompat;
linyuhc3968e62017-11-20 17:40:50 -080063import com.android.dialer.compat.CompatUtils;
Eric Erfanian2ca43182017-08-31 06:57:16 -070064import com.android.dialer.configprovider.ConfigProviderBindings;
twyen73a74c32018-03-07 12:12:24 -080065import com.android.dialer.logging.DialerImpression.Type;
Eric Erfanianccca3152017-02-22 16:32:36 -080066import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070067import com.android.dialer.logging.ScreenEvent;
zachh7a96dc72018-02-20 22:16:03 -080068import com.android.dialer.metrics.Metrics;
69import com.android.dialer.metrics.MetricsComponent;
twyen73a74c32018-03-07 12:12:24 -080070import com.android.dialer.preferredsim.PreferredAccountRecorder;
71import com.android.dialer.preferredsim.PreferredAccountWorker;
72import com.android.dialer.preferredsim.suggestion.SuggestionProvider;
linyuhc3968e62017-11-20 17:40:50 -080073import com.android.dialer.util.ViewUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import com.android.incallui.answer.bindings.AnswerBindings;
75import com.android.incallui.answer.protocol.AnswerScreen;
76import com.android.incallui.answer.protocol.AnswerScreenDelegate;
77import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
78import com.android.incallui.answerproximitysensor.PseudoScreenState;
linyuh57b093b2017-11-17 14:32:32 -080079import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080080import com.android.incallui.call.CallList;
81import com.android.incallui.call.DialerCall;
82import com.android.incallui.call.DialerCall.State;
linyuh57b093b2017-11-17 14:32:32 -080083import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070084import com.android.incallui.callpending.CallPendingActivity;
85import com.android.incallui.disconnectdialog.DisconnectMessage;
Eric Erfanianccca3152017-02-22 16:32:36 -080086import com.android.incallui.incall.bindings.InCallBindings;
87import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
88import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
89import com.android.incallui.incall.protocol.InCallScreen;
90import com.android.incallui.incall.protocol.InCallScreenDelegate;
91import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
linyuh7b86f562017-11-16 11:24:09 -080092import com.android.incallui.incalluilock.InCallUiLock;
wangqi219b8702018-02-13 09:34:41 -080093import com.android.incallui.rtt.bindings.RttBindings;
94import com.android.incallui.rtt.protocol.RttCallScreen;
95import com.android.incallui.rtt.protocol.RttCallScreenDelegate;
96import com.android.incallui.rtt.protocol.RttCallScreenDelegateFactory;
linyuhf99f6302017-11-15 11:23:51 -080097import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080098import com.android.incallui.video.bindings.VideoBindings;
99import com.android.incallui.video.protocol.VideoCallScreen;
100import com.android.incallui.video.protocol.VideoCallScreenDelegate;
101import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
linyuhc3968e62017-11-20 17:40:50 -0800102import com.google.common.base.Optional;
103import java.lang.annotation.Retention;
104import java.lang.annotation.RetentionPolicy;
105import java.util.ArrayList;
106import java.util.List;
Eric Erfanianccca3152017-02-22 16:32:36 -0800107
108/** Version of {@link InCallActivity} that shows the new UI */
109public class InCallActivity extends TransactionSafeFragmentActivity
110 implements AnswerScreenDelegateFactory,
111 InCallScreenDelegateFactory,
112 InCallButtonUiDelegateFactory,
113 VideoCallScreenDelegateFactory,
wangqi219b8702018-02-13 09:34:41 -0800114 RttCallScreenDelegateFactory,
Eric Erfanianccca3152017-02-22 16:32:36 -0800115 PseudoScreenState.StateChangedListener {
116
linyuhc3968e62017-11-20 17:40:50 -0800117 @Retention(RetentionPolicy.SOURCE)
118 @IntDef({
119 DIALPAD_REQUEST_NONE,
120 DIALPAD_REQUEST_SHOW,
121 DIALPAD_REQUEST_HIDE,
122 })
123 @interface DialpadRequestType {}
Eric Erfanian2ca43182017-08-31 06:57:16 -0700124
linyuhc3968e62017-11-20 17:40:50 -0800125 private static final int DIALPAD_REQUEST_NONE = 1;
126 private static final int DIALPAD_REQUEST_SHOW = 2;
127 private static final int DIALPAD_REQUEST_HIDE = 3;
linyuh57b093b2017-11-17 14:32:32 -0800128
linyuhc3968e62017-11-20 17:40:50 -0800129 private static Optional<Integer> audioRouteForTesting = Optional.absent();
linyuh57b093b2017-11-17 14:32:32 -0800130
linyuhc3968e62017-11-20 17:40:50 -0800131 private final InternationalCallOnWifiCallback internationalCallOnWifiCallback =
132 new InternationalCallOnWifiCallback();
twyen73a74c32018-03-07 12:12:24 -0800133
134 private SelectPhoneAccountListener selectPhoneAccountListener;
Eric Erfanianccca3152017-02-22 16:32:36 -0800135
linyuhc3968e62017-11-20 17:40:50 -0800136 private Animation dialpadSlideInAnimation;
137 private Animation dialpadSlideOutAnimation;
138 private Dialog errorDialog;
139 private GradientDrawable backgroundDrawable;
linyuh69a25062017-11-15 16:18:51 -0800140 private InCallOrientationEventListener inCallOrientationEventListener;
linyuhc3968e62017-11-20 17:40:50 -0800141 private View pseudoBlackScreenOverlay;
142 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
143 private String dtmfTextToPrepopulate;
144 private String showPostCharWaitDialogCallId;
145 private String showPostCharWaitDialogChars;
146 private boolean allowOrientationChange;
147 private boolean animateDialpadOnShow;
Eric Erfanianccca3152017-02-22 16:32:36 -0800148 private boolean didShowAnswerScreen;
149 private boolean didShowInCallScreen;
150 private boolean didShowVideoCallScreen;
wangqi219b8702018-02-13 09:34:41 -0800151 private boolean didShowRttCallScreen;
linyuh9c327da2017-11-14 12:33:48 -0800152 private boolean dismissKeyguard;
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 private boolean isInShowMainInCallFragment;
linyuhc3968e62017-11-20 17:40:50 -0800154 private boolean isRecreating; // whether the activity is going to be recreated
155 private boolean isVisible;
Eric Erfanianccca3152017-02-22 16:32:36 -0800156 private boolean needDismissPendingDialogs;
linyuhc3968e62017-11-20 17:40:50 -0800157 private boolean showPostCharWaitDialogOnResume;
158 private boolean touchDownWhenPseudoScreenOff;
159 private int[] backgroundDrawableColors;
160 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800161
162 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700163 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800164 Intent intent = new Intent(Intent.ACTION_MAIN, null);
165 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
166 intent.setClass(context, InCallActivity.class);
linyuhc3968e62017-11-20 17:40:50 -0800167 if (showDialpad) {
168 intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true);
169 }
170 intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall);
171 intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800172 return intent;
173 }
174
175 @Override
176 protected void onResumeFragments() {
177 super.onResumeFragments();
178 if (needDismissPendingDialogs) {
179 dismissPendingDialogs();
180 }
181 }
182
183 @Override
linyuhc3968e62017-11-20 17:40:50 -0800184 protected void onCreate(Bundle bundle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700185 Trace.beginSection("InCallActivity.onCreate");
linyuhc3968e62017-11-20 17:40:50 -0800186 super.onCreate(bundle);
Eric Erfanianccca3152017-02-22 16:32:36 -0800187
twyen73a74c32018-03-07 12:12:24 -0800188 selectPhoneAccountListener = new SelectPhoneAccountListener(getApplicationContext());
189
linyuhc3968e62017-11-20 17:40:50 -0800190 if (bundle != null) {
191 didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
192 didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
193 didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
wangqi153af2f2018-02-15 16:21:49 -0800194 didShowRttCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800195 }
196
linyuhc3968e62017-11-20 17:40:50 -0800197 setWindowFlags();
198 setContentView(R.layout.incall_screen);
199 internalResolveIntent(getIntent());
200
201 boolean isLandscape =
202 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
203 boolean isRtl = ViewUtil.isRtl();
204 if (isLandscape) {
205 dialpadSlideInAnimation =
206 AnimationUtils.loadAnimation(
207 this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
208 dialpadSlideOutAnimation =
209 AnimationUtils.loadAnimation(
210 this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
211 } else {
212 dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
213 dialpadSlideOutAnimation =
214 AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
215 }
216 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
217 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
218 dialpadSlideOutAnimation.setAnimationListener(
219 new AnimationListenerAdapter() {
220 @Override
221 public void onAnimationEnd(Animation animation) {
222 hideDialpadFragment();
223 }
224 });
225
226 if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
227 // If the dialpad was shown before, set related variables so that it can be shown and
228 // populated with the previous DTMF text during onResume().
229 if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) {
230 boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD);
231 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
232 animateDialpadOnShow = false;
233 }
234 dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT);
235
236 SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
237 (SelectPhoneAccountDialogFragment)
238 getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT);
239 if (selectPhoneAccountDialogFragment != null) {
240 selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener);
241 }
242 }
243
244 InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment =
245 (InternationalCallOnWifiDialogFragment)
246 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
247 if (existingInternationalCallOnWifiDialogFragment != null) {
248 existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback);
249 }
250
linyuh69a25062017-11-15 16:18:51 -0800251 inCallOrientationEventListener = new InCallOrientationEventListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800252
253 getWindow()
254 .getDecorView()
255 .setSystemUiVisibility(
256 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
257
258 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700259 sendBroadcast(CallPendingActivity.getFinishBroadcast());
260 Trace.endSection();
zachh7a96dc72018-02-20 22:16:03 -0800261 MetricsComponent.get(this)
262 .metrics()
263 .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
264 MetricsComponent.get(this)
265 .metrics()
266 .stopTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800267 }
268
linyuhc3968e62017-11-20 17:40:50 -0800269 private void setWindowFlags() {
270 // Allow the activity to be shown when the screen is locked and filter out touch events that are
271 // "too fat".
272 int flags =
273 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
274 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
275
linyuhf79d1cb2017-12-15 17:49:56 -0800276 // When the audio stream is not via Bluetooth, turn on the screen once the activity is shown.
277 // When the audio stream is via Bluetooth, turn on the screen only for an incoming call.
linyuhc3968e62017-11-20 17:40:50 -0800278 final int audioRoute = getAudioRoute();
linyuhf79d1cb2017-12-15 17:49:56 -0800279 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH
280 || CallList.getInstance().getIncomingCall() != null) {
linyuhc3968e62017-11-20 17:40:50 -0800281 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
282 }
283
284 getWindow().addFlags(flags);
285 }
286
287 private static int getAudioRoute() {
288 if (audioRouteForTesting.isPresent()) {
289 return audioRouteForTesting.get();
290 }
291
292 return AudioModeProvider.getInstance().getAudioState().getRoute();
293 }
294
295 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
296 public static void setAudioRouteForTesting(int audioRoute) {
297 audioRouteForTesting = Optional.of(audioRoute);
298 }
299
300 private void internalResolveIntent(Intent intent) {
301 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
302 return;
303 }
304
305 if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) {
306 // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be
307 // initially visible. If the extra is absent, leave the dialpad in its previous state.
308 boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false);
309 relaunchedFromDialer(showDialpad);
310 }
311
312 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
313 if (outgoingCall == null) {
314 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
315 }
316 if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) {
317 intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL);
318
319 // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of
320 // making it (i.e. no valid call capable accounts).
321 // If the version is not MSIM compatible, ignore this code.
322 if (CompatUtils.isMSIMCompatible()
323 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
324 LogUtil.i(
325 "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting");
326 outgoingCall.disconnect();
327 }
328
329 dismissKeyguard(true);
330 }
331
332 if (showPhoneAccountSelectionDialog()) {
333 hideMainInCallFragment();
334 }
335 }
336
337 /**
338 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
339 * be shown on launch.
340 *
341 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
342 * false} to indicate no change should be made to the dialpad visibility.
343 */
344 private void relaunchedFromDialer(boolean showDialpad) {
345 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
346 animateDialpadOnShow = true;
347
348 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
349 // If there's only one line in use, AND it's on hold, then we're sure the user
350 // wants to use the dialpad toward the exact line, so un-hold the holding line.
351 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
352 if (call != null && call.getState() == State.ONHOLD) {
353 call.unhold();
354 }
355 }
356 }
357
358 /**
359 * Show a phone account selection dialog if there is a call waiting for phone account selection.
360 *
361 * @return true if the dialog was shown.
362 */
363 private boolean showPhoneAccountSelectionDialog() {
364 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
365 if (waitingForAccountCall == null) {
366 return false;
367 }
368
twyen73a74c32018-03-07 12:12:24 -0800369 DialerExecutorComponent.get(this)
370 .dialerExecutorFactory()
371 .createNonUiTaskBuilder(new PreferredAccountWorker(waitingForAccountCall.getNumber()))
372 .onSuccess(
373 (result -> {
374 if (result.getPhoneAccountHandle().isPresent()) {
375 Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_PREFERRED_USED);
376 selectPhoneAccountListener.onPhoneAccountSelected(
377 result.getPhoneAccountHandle().get(), false, waitingForAccountCall.getId());
378 return;
379 }
380 if (result.getSuggestion().isPresent()) {
381 LogUtil.i(
382 "CallingAccountSelector.processPreferredAccount",
383 "SIM suggested: " + result.getSuggestion().get().reason);
384 if (result.getSuggestion().get().shouldAutoSelect) {
385 Logger.get(this).logImpression(Type.DUAL_SIM_SELECTION_SUGGESTION_AUTO_SELECTED);
386 LogUtil.i(
387 "CallingAccountSelector.processPreferredAccount", "Auto selected suggestion");
388 selectPhoneAccountListener.onPhoneAccountSelected(
389 result.getSuggestion().get().phoneAccountHandle,
390 false,
391 waitingForAccountCall.getId());
392 return;
393 }
394 }
395 Bundle extras = waitingForAccountCall.getIntentExtras();
396 List<PhoneAccountHandle> phoneAccountHandles =
397 extras == null
398 ? new ArrayList<>()
399 : extras.getParcelableArrayList(Call.AVAILABLE_PHONE_ACCOUNTS);
linyuhc3968e62017-11-20 17:40:50 -0800400
twyen73a74c32018-03-07 12:12:24 -0800401 waitingForAccountCall.setPreferredAccountRecorder(
402 new PreferredAccountRecorder(
403 waitingForAccountCall.getNumber(),
404 result.getSuggestion().orNull(),
405 result.getDataId().orNull()));
406 selectPhoneAccountDialogFragment =
407 SelectPhoneAccountDialogFragment.newInstance(
408 R.string.select_phone_account_for_calls,
409 result.getDataId().isPresent() /* canSetDefault */,
410 R.string.select_phone_account_for_calls_remember /* setDefaultResId */,
411 phoneAccountHandles,
412 selectPhoneAccountListener,
413 waitingForAccountCall.getId(),
414 SuggestionProvider.buildHint(
415 this, phoneAccountHandles, result.getSuggestion().orNull() /* hints */));
416 selectPhoneAccountDialogFragment.show(
417 getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
418 }))
419 .build()
420 .executeParallel(this);
421
linyuhc3968e62017-11-20 17:40:50 -0800422 return true;
423 }
424
Eric Erfanianccca3152017-02-22 16:32:36 -0800425 @Override
426 protected void onSaveInstanceState(Bundle out) {
linyuh57b093b2017-11-17 14:32:32 -0800427 LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
428
429 // TODO: DialpadFragment should handle this as part of its own state
linyuhc3968e62017-11-20 17:40:50 -0800430 out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible());
linyuh57b093b2017-11-17 14:32:32 -0800431 DialpadFragment dialpadFragment = getDialpadFragment();
432 if (dialpadFragment != null) {
linyuhc3968e62017-11-20 17:40:50 -0800433 out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText());
linyuh57b093b2017-11-17 14:32:32 -0800434 }
435
linyuhc3968e62017-11-20 17:40:50 -0800436 out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
437 out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
438 out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
wangqi153af2f2018-02-15 16:21:49 -0800439 out.putBoolean(KeysForSavedInstance.DID_SHOW_RTT_CALL_SCREEN, didShowRttCallScreen);
linyuh57b093b2017-11-17 14:32:32 -0800440
Eric Erfanianccca3152017-02-22 16:32:36 -0800441 super.onSaveInstanceState(out);
442 isVisible = false;
443 }
444
445 @Override
446 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700447 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800448 super.onStart();
linyuh57b093b2017-11-17 14:32:32 -0800449
Eric Erfanianccca3152017-02-22 16:32:36 -0800450 isVisible = true;
451 showMainInCallFragment();
linyuh57b093b2017-11-17 14:32:32 -0800452
453 InCallPresenter.getInstance().setActivity(this);
454 enableInCallOrientationEventListener(
455 getRequestedOrientation()
456 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
457 InCallPresenter.getInstance().onActivityStarted();
458
yueg10f6e822018-01-17 15:32:18 -0800459 if (!isRecreating) {
460 InCallPresenter.getInstance().onUiShowing(true);
461 }
462
Eric Erfanianccca3152017-02-22 16:32:36 -0800463 if (ActivityCompat.isInMultiWindowMode(this)
464 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
465 // Hide the dialpad because there may not be enough room
466 showDialpadFragment(false, false);
467 }
linyuh57b093b2017-11-17 14:32:32 -0800468
Eric Erfanian2ca43182017-08-31 06:57:16 -0700469 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 }
471
472 @Override
473 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700474 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 super.onResume();
linyuhc3968e62017-11-20 17:40:50 -0800476
477 if (!InCallPresenter.getInstance().isReadyForTearDown()) {
478 updateTaskDescription();
yueg10f6e822018-01-17 15:32:18 -0800479 InCallPresenter.getInstance().updateNotification();
linyuhc3968e62017-11-20 17:40:50 -0800480 }
481
482 // If there is a pending request to show or hide the dialpad, handle that now.
483 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
484 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
485 // Exit fullscreen so that the user has access to the dialpad hide/show button.
486 // This is important when showing the dialpad from within dialer.
487 InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */);
488
489 showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
490 animateDialpadOnShow = false;
491
492 DialpadFragment dialpadFragment = getDialpadFragment();
493 if (dialpadFragment != null) {
494 dialpadFragment.setDtmfText(dtmfTextToPrepopulate);
495 dtmfTextToPrepopulate = null;
496 }
497 } else {
498 LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad");
499 if (getDialpadFragment() != null) {
500 showDialpadFragment(false /* show */, false /* animate */);
501 }
502 }
503 showDialpadRequest = DIALPAD_REQUEST_NONE;
504 }
505 updateNavigationBar(isDialpadVisible());
506
507 if (showPostCharWaitDialogOnResume) {
508 showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
509 }
510
511 CallList.getInstance()
512 .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false));
513
Eric Erfanianccca3152017-02-22 16:32:36 -0800514 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
515 pseudoScreenState.addListener(this);
516 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700517 Trace.endSection();
weijiaxu650e7cc2017-10-31 12:38:54 -0700518 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
519 ThreadUtil.postDelayedOnUiThread(
weijiaxuc950a9b2017-11-06 16:39:04 -0800520 () ->
zachh7a96dc72018-02-20 22:16:03 -0800521 MetricsComponent.get(this)
522 .metrics()
523 .recordMemory(Metrics.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME),
weijiaxu650e7cc2017-10-31 12:38:54 -0700524 1000);
Eric Erfanianccca3152017-02-22 16:32:36 -0800525 }
526
Eric Erfanianccca3152017-02-22 16:32:36 -0800527 @Override
528 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700529 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800530 super.onPause();
linyuh57b093b2017-11-17 14:32:32 -0800531
532 DialpadFragment dialpadFragment = getDialpadFragment();
533 if (dialpadFragment != null) {
534 dialpadFragment.onDialerKeyUp(null);
535 }
536
yueg10f6e822018-01-17 15:32:18 -0800537 InCallPresenter.getInstance().updateNotification();
linyuh57b093b2017-11-17 14:32:32 -0800538
Eric Erfanianccca3152017-02-22 16:32:36 -0800539 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700540 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800541 }
542
543 @Override
544 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700545 Trace.beginSection("InCallActivity.onStop");
wangqi4d705e52017-09-28 12:23:35 -0700546 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800547 super.onStop();
linyuh57b093b2017-11-17 14:32:32 -0800548
549 // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
550 // user presses the home button).
551 // Without this the pending call will get stuck on phone account selection and new calls can't
552 // be created.
553 // Skip this when the screen is locked since the activity may complete its current life cycle
554 // and restart.
linyuhc3968e62017-11-20 17:40:50 -0800555 if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
linyuh57b093b2017-11-17 14:32:32 -0800556 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
557 if (waitingForAccountCall != null) {
558 waitingForAccountCall.disconnect();
559 }
560 }
561
562 enableInCallOrientationEventListener(false);
563 InCallPresenter.getInstance().updateIsChangingConfigurations();
564 InCallPresenter.getInstance().onActivityStopped();
linyuhc3968e62017-11-20 17:40:50 -0800565 if (!isRecreating) {
yueg10f6e822018-01-17 15:32:18 -0800566 InCallPresenter.getInstance().onUiShowing(false);
linyuh57b093b2017-11-17 14:32:32 -0800567 if (errorDialog != null) {
568 errorDialog.dismiss();
569 }
570 }
571
yueg10f6e822018-01-17 15:32:18 -0800572 if (isFinishing()) {
573 InCallPresenter.getInstance().unsetActivity(this);
574 }
575
Eric Erfanian2ca43182017-08-31 06:57:16 -0700576 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 }
578
579 @Override
580 protected void onDestroy() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700581 Trace.beginSection("InCallActivity.onDestroy");
Eric Erfanianccca3152017-02-22 16:32:36 -0800582 super.onDestroy();
linyuh57b093b2017-11-17 14:32:32 -0800583
584 InCallPresenter.getInstance().unsetActivity(this);
585 InCallPresenter.getInstance().updateIsChangingConfigurations();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700586 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800587 }
588
589 @Override
590 public void finish() {
591 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700592 // When user select incall ui from recents after the call is disconnected, it tries to launch
593 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
594 // crash.
595 // By calling finishAndRemoveTask() instead of finish() the task associated with
596 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
597 // this case.
598 //
599 // Calling finish won't clear the task and normally when an activity finishes it shouldn't
600 // clear the task since there could be parent activity in the same task that's still alive.
601 // But InCallActivity is special since it's singleInstance which means it's root activity and
602 // only instance of activity in the task. So it should be safe to also remove task when
603 // finishing.
604 // It's also necessary in the sense of it's excluded from recents. So whenever the activity
605 // finishes, the task should also be removed since it doesn't make sense to go back to it in
606 // anyway anymore.
607 super.finishAndRemoveTask();
Eric Erfanianccca3152017-02-22 16:32:36 -0800608 }
609 }
610
611 private boolean shouldCloseActivityOnFinish() {
linyuhc3968e62017-11-20 17:40:50 -0800612 if (!isVisible) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 LogUtil.i(
614 "InCallActivity.shouldCloseActivityOnFinish",
615 "allowing activity to be closed because it's not visible");
616 return true;
617 }
618
twyen8efb4952017-10-06 16:35:54 -0700619 if (InCallPresenter.getInstance().isInCallUiLocked()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800620 LogUtil.i(
621 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700622 "in call ui is locked, not closing activity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800623 return false;
624 }
625
626 LogUtil.i(
627 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700628 "activity is visible and has no locks, allowing activity to close");
Eric Erfanianccca3152017-02-22 16:32:36 -0800629 return true;
630 }
631
632 @Override
633 protected void onNewIntent(Intent intent) {
linyuhc3968e62017-11-20 17:40:50 -0800634 LogUtil.enterBlock("InCallActivity.onNewIntent");
Eric Erfanianccca3152017-02-22 16:32:36 -0800635
636 // If the screen is off, we need to make sure it gets turned on for incoming calls.
637 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
638 // when the activity is first created. Therefore, to ensure the screen is turned on
639 // for the call waiting case, we recreate() the current activity. There should be no jank from
640 // this since the screen is already off and will remain so until our new activity is up.
linyuhc3968e62017-11-20 17:40:50 -0800641 if (!isVisible) {
642 onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800643 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
644 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700645 } else {
linyuhc3968e62017-11-20 17:40:50 -0800646 onNewIntent(intent, false /* isRecreating */);
647 }
648 }
649
yuega3305352018-01-09 11:02:47 -0800650 @VisibleForTesting
651 void onNewIntent(Intent intent, boolean isRecreating) {
linyuhc3968e62017-11-20 17:40:50 -0800652 this.isRecreating = isRecreating;
653
654 // We're being re-launched with a new Intent. Since it's possible for a single InCallActivity
655 // instance to persist indefinitely (even if we finish() ourselves), this sequence can
656 // happen any time the InCallActivity needs to be displayed.
657
658 // Stash away the new intent so that we can get it in the future by calling getIntent().
659 // Otherwise getIntent() will return the original Intent from when we first got created.
660 setIntent(intent);
661
662 // Activities are always paused before receiving a new intent, so we can count on our onResume()
663 // method being called next.
664
665 // Just like in onCreate(), handle the intent.
666 // Skip if InCallActivity is going to be recreated since this will be called in onCreate().
667 if (!isRecreating) {
668 internalResolveIntent(intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800669 }
670 }
671
672 @Override
673 public void onBackPressed() {
linyuh57b093b2017-11-17 14:32:32 -0800674 LogUtil.enterBlock("InCallActivity.onBackPressed");
675
linyuhc3968e62017-11-20 17:40:50 -0800676 if (!isVisible) {
linyuh57b093b2017-11-17 14:32:32 -0800677 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800678 }
linyuh57b093b2017-11-17 14:32:32 -0800679
680 if (!getCallCardFragmentVisible()) {
681 return;
682 }
683
684 DialpadFragment dialpadFragment = getDialpadFragment();
685 if (dialpadFragment != null && dialpadFragment.isVisible()) {
686 showDialpadFragment(false /* show */, true /* animate */);
687 return;
688 }
689
690 if (CallList.getInstance().getIncomingCall() != null) {
691 LogUtil.i(
692 "InCallActivity.onBackPressed",
693 "Ignore the press of the back key when an incoming call is ringing");
694 return;
695 }
696
697 // Nothing special to do. Fall back to the default behavior.
698 super.onBackPressed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800699 }
700
701 @Override
702 public boolean onOptionsItemSelected(MenuItem item) {
703 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
704 if (item.getItemId() == android.R.id.home) {
705 onBackPressed();
706 return true;
707 }
708 return super.onOptionsItemSelected(item);
709 }
710
711 @Override
712 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800713 DialpadFragment dialpadFragment = getDialpadFragment();
714 if (dialpadFragment != null
715 && dialpadFragment.isVisible()
716 && dialpadFragment.onDialerKeyUp(event)) {
717 return true;
718 }
719
720 if (keyCode == KeyEvent.KEYCODE_CALL) {
721 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
722 return true;
723 }
724
725 return super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800726 }
727
728 @Override
729 public boolean onKeyDown(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800730 switch (keyCode) {
731 case KeyEvent.KEYCODE_CALL:
732 if (!InCallPresenter.getInstance().handleCallKey()) {
733 LogUtil.e(
734 "InCallActivity.onKeyDown",
735 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
736 }
737 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
738 return true;
739
740 // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
741 // is exactly what's needed, namely
742 // (1) "hang up" if there's an active call, or
743 // (2) "don't answer" if there's an incoming call.
744 // (See PhoneWindowManager for implementation details.)
745
746 case KeyEvent.KEYCODE_CAMERA:
747 // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
748 return true;
749
750 case KeyEvent.KEYCODE_VOLUME_UP:
751 case KeyEvent.KEYCODE_VOLUME_DOWN:
752 case KeyEvent.KEYCODE_VOLUME_MUTE:
753 // Ringer silencing handled by PhoneWindowManager.
754 break;
755
756 case KeyEvent.KEYCODE_MUTE:
757 TelecomAdapter.getInstance()
758 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
759 return true;
760
761 case KeyEvent.KEYCODE_SLASH:
762 // When verbose logging is enabled, dump the view for debugging/testing purposes.
763 if (LogUtil.isVerboseEnabled()) {
764 View decorView = getWindow().getDecorView();
765 LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
766 return true;
767 }
768 break;
769
770 case KeyEvent.KEYCODE_EQUALS:
771 break;
772
773 default: // fall out
774 }
775
776 // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
777 // in DTMF (Dual-tone multi-frequency signaling) code.
778 DialpadFragment dialpadFragment = getDialpadFragment();
779 if (dialpadFragment != null
780 && dialpadFragment.isVisible()
781 && dialpadFragment.onDialerKeyDown(event)) {
782 return true;
783 }
784
785 return super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800786 }
787
788 public boolean isInCallScreenAnimating() {
789 return false;
790 }
791
792 public void showConferenceFragment(boolean show) {
793 if (show) {
794 startActivity(new Intent(this, ManageConferenceActivity.class));
795 }
796 }
797
linyuhc3968e62017-11-20 17:40:50 -0800798 public void showDialpadFragment(boolean show, boolean animate) {
799 if (show == isDialpadVisible()) {
800 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800801 }
linyuhc3968e62017-11-20 17:40:50 -0800802
803 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
804 if (dialpadFragmentManager == null) {
805 LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager");
806 return;
807 }
808
809 if (!animate) {
810 if (show) {
811 showDialpadFragment();
812 } else {
813 hideDialpadFragment();
814 }
815 } else {
816 if (show) {
817 showDialpadFragment();
818 getDialpadFragment().animateShowDialpad();
819 }
820 getDialpadFragment()
821 .getView()
822 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
823 }
824
825 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
826 if (sensor != null) {
827 sensor.onDialpadVisible(show);
828 }
829 showDialpadRequest = DIALPAD_REQUEST_NONE;
830
831 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
832 // repositions itself.
wangqifd4c9f72018-03-08 18:21:50 -0800833 getInCallOrRttCallScreen().onInCallScreenDialpadVisibilityChange(show);
linyuhc3968e62017-11-20 17:40:50 -0800834 }
835
836 private void showDialpadFragment() {
837 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
838 if (dialpadFragmentManager == null) {
839 return;
840 }
841
842 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
843 DialpadFragment dialpadFragment = getDialpadFragment();
844 if (dialpadFragment == null) {
wangqifd4c9f72018-03-08 18:21:50 -0800845 dialpadFragment = new DialpadFragment();
846 transaction.add(getDialpadContainerId(), dialpadFragment, Tags.DIALPAD_FRAGMENT);
linyuhc3968e62017-11-20 17:40:50 -0800847 } else {
848 transaction.show(dialpadFragment);
calderwoodrad5883872017-12-12 15:29:12 -0800849 dialpadFragment.setUserVisibleHint(true);
linyuhc3968e62017-11-20 17:40:50 -0800850 }
wangqifd4c9f72018-03-08 18:21:50 -0800851 // RTT call screen doesn't show end call button inside dialpad, thus the space reserved for end
852 // call button should be removed.
853 dialpadFragment.setShouldShowEndCallSpace(didShowInCallScreen);
linyuhc3968e62017-11-20 17:40:50 -0800854 transaction.commitAllowingStateLoss();
855 dialpadFragmentManager.executePendingTransactions();
856
857 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this);
858 updateNavigationBar(true /* isDialpadVisible */);
859 }
860
861 private void hideDialpadFragment() {
862 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
863 if (dialpadFragmentManager == null) {
864 return;
865 }
866
calderwoodrad5883872017-12-12 15:29:12 -0800867 DialpadFragment dialpadFragment = getDialpadFragment();
linyuhc3968e62017-11-20 17:40:50 -0800868 if (dialpadFragment != null) {
869 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
870 transaction.hide(dialpadFragment);
871 transaction.commitAllowingStateLoss();
872 dialpadFragmentManager.executePendingTransactions();
calderwoodrad5883872017-12-12 15:29:12 -0800873 dialpadFragment.setUserVisibleHint(false);
linyuhc3968e62017-11-20 17:40:50 -0800874 }
875 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800876 }
877
878 public boolean isDialpadVisible() {
linyuh69a25062017-11-15 16:18:51 -0800879 DialpadFragment dialpadFragment = getDialpadFragment();
calderwoodraa584bcd2018-01-24 12:19:56 -0800880 return dialpadFragment != null
881 && dialpadFragment.isAdded()
882 && !dialpadFragment.isHidden()
883 && dialpadFragment.getView() != null
884 && dialpadFragment.getUserVisibleHint();
linyuh69a25062017-11-15 16:18:51 -0800885 }
886
linyuhc3968e62017-11-20 17:40:50 -0800887 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
linyuh69a25062017-11-15 16:18:51 -0800888 @Nullable
linyuhc3968e62017-11-20 17:40:50 -0800889 private DialpadFragment getDialpadFragment() {
linyuh69a25062017-11-15 16:18:51 -0800890 FragmentManager fragmentManager = getDialpadFragmentManager();
891 if (fragmentManager == null) {
892 return null;
893 }
linyuhc3968e62017-11-20 17:40:50 -0800894 return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800895 }
896
897 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh57b093b2017-11-17 14:32:32 -0800898 updateTaskDescription();
899
900 if (newForegroundCall == null || !didShowAnswerScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800901 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
linyuh57b093b2017-11-17 14:32:32 -0800902 updateWindowBackgroundColor(0 /* progress */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800903 }
904 }
905
linyuhc3968e62017-11-20 17:40:50 -0800906 private void updateTaskDescription() {
linyuh57b093b2017-11-17 14:32:32 -0800907 int color =
908 getResources().getBoolean(R.bool.is_layout_landscape)
909 ? ResourcesCompat.getColor(
910 getResources(), R.color.statusbar_background_color, getTheme())
911 : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
912 setTaskDescription(
913 new TaskDescription(
914 getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
915 }
916
Eric Erfanianccca3152017-02-22 16:32:36 -0800917 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
918 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
919 @ColorInt int top;
920 @ColorInt int middle;
921 @ColorInt int bottom;
922 @ColorInt int gray = 0x66000000;
923
924 if (ActivityCompat.isInMultiWindowMode(this)) {
925 top = themeColorManager.getBackgroundColorSolid();
926 middle = themeColorManager.getBackgroundColorSolid();
927 bottom = themeColorManager.getBackgroundColorSolid();
928 } else {
929 top = themeColorManager.getBackgroundColorTop();
930 middle = themeColorManager.getBackgroundColorMiddle();
931 bottom = themeColorManager.getBackgroundColorBottom();
932 }
933
934 if (progress < 0) {
935 float correctedProgress = Math.abs(progress);
936 top = ColorUtils.blendARGB(top, gray, correctedProgress);
937 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
938 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
939 }
940
941 boolean backgroundDirty = false;
942 if (backgroundDrawable == null) {
943 backgroundDrawableColors = new int[] {top, middle, bottom};
944 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
945 backgroundDirty = true;
946 } else {
947 if (backgroundDrawableColors[0] != top) {
948 backgroundDrawableColors[0] = top;
949 backgroundDirty = true;
950 }
951 if (backgroundDrawableColors[1] != middle) {
952 backgroundDrawableColors[1] = middle;
953 backgroundDirty = true;
954 }
955 if (backgroundDrawableColors[2] != bottom) {
956 backgroundDrawableColors[2] = bottom;
957 backgroundDirty = true;
958 }
959 if (backgroundDirty) {
960 backgroundDrawable.setColors(backgroundDrawableColors);
961 }
962 }
963
964 if (backgroundDirty) {
965 getWindow().setBackgroundDrawable(backgroundDrawable);
966 }
967 }
968
969 public boolean isVisible() {
970 return isVisible;
971 }
972
973 public boolean getCallCardFragmentVisible() {
wangqifd4c9f72018-03-08 18:21:50 -0800974 return didShowInCallScreen || didShowVideoCallScreen || didShowRttCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -0800975 }
976
977 public void dismissKeyguard(boolean dismiss) {
linyuh9c327da2017-11-14 12:33:48 -0800978 if (dismissKeyguard == dismiss) {
979 return;
980 }
981
982 dismissKeyguard = dismiss;
983 if (dismiss) {
984 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
985 } else {
986 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
987 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800988 }
989
linyuhc3968e62017-11-20 17:40:50 -0800990 public void showDialogForPostCharWait(String callId, String chars) {
991 if (isVisible) {
992 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
993 fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT);
994
995 showPostCharWaitDialogOnResume = false;
996 showPostCharWaitDialogCallId = null;
997 showPostCharWaitDialogChars = null;
998 } else {
999 showPostCharWaitDialogOnResume = true;
1000 showPostCharWaitDialogCallId = callId;
1001 showPostCharWaitDialogChars = chars;
1002 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001003 }
1004
linyuh7b86f562017-11-16 11:24:09 -08001005 public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
1006 LogUtil.i(
1007 "InCallActivity.showDialogOrToastForDisconnectedCall",
1008 "disconnect cause: %s",
1009 disconnectMessage);
1010
1011 if (disconnectMessage.dialog == null || isFinishing()) {
1012 return;
1013 }
1014
1015 dismissPendingDialogs();
1016
1017 // Show a toast if the app is in background when a dialog can't be visible.
1018 if (!isVisible()) {
1019 Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
1020 .show();
1021 return;
1022 }
1023
1024 // Show the dialog.
linyuhc3968e62017-11-20 17:40:50 -08001025 errorDialog = disconnectMessage.dialog;
linyuh7b86f562017-11-16 11:24:09 -08001026 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
1027 disconnectMessage.dialog.setOnDismissListener(
1028 dialogInterface -> {
1029 lock.release();
1030 onDialogDismissed();
1031 });
1032 disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
1033 disconnectMessage.dialog.show();
1034 }
1035
1036 private void onDialogDismissed() {
linyuhc3968e62017-11-20 17:40:50 -08001037 errorDialog = null;
linyuh7b86f562017-11-16 11:24:09 -08001038 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -08001039 }
1040
1041 public void dismissPendingDialogs() {
linyuhc3968e62017-11-20 17:40:50 -08001042 LogUtil.enterBlock("InCallActivity.dismissPendingDialogs");
linyuhf99f6302017-11-15 11:23:51 -08001043
1044 if (!isVisible) {
1045 // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
1046 // been called.
Eric Erfanianccca3152017-02-22 16:32:36 -08001047 LogUtil.i(
1048 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
1049 needDismissPendingDialogs = true;
linyuhf99f6302017-11-15 11:23:51 -08001050 return;
Eric Erfanianccca3152017-02-22 16:32:36 -08001051 }
linyuhf99f6302017-11-15 11:23:51 -08001052
1053 // Dismiss the error dialog
linyuhf99f6302017-11-15 11:23:51 -08001054 if (errorDialog != null) {
1055 errorDialog.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001056 errorDialog = null;
linyuhf99f6302017-11-15 11:23:51 -08001057 }
1058
1059 // Dismiss the phone account selection dialog
linyuhf99f6302017-11-15 11:23:51 -08001060 if (selectPhoneAccountDialogFragment != null) {
1061 selectPhoneAccountDialogFragment.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001062 selectPhoneAccountDialogFragment = null;
linyuhf99f6302017-11-15 11:23:51 -08001063 }
1064
1065 // Dismiss the dialog for international call on WiFi
1066 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
1067 (InternationalCallOnWifiDialogFragment)
linyuhc3968e62017-11-20 17:40:50 -08001068 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
linyuhf99f6302017-11-15 11:23:51 -08001069 if (internationalCallOnWifiFragment != null) {
1070 internationalCallOnWifiFragment.dismiss();
1071 }
1072
1073 // Dismiss the answer screen
1074 AnswerScreen answerScreen = getAnswerScreen();
1075 if (answerScreen != null) {
1076 answerScreen.dismissPendingDialogs();
1077 }
1078
1079 needDismissPendingDialogs = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001080 }
1081
linyuhc3968e62017-11-20 17:40:50 -08001082 private void enableInCallOrientationEventListener(boolean enable) {
linyuh69a25062017-11-15 16:18:51 -08001083 if (enable) {
1084 inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
1085 } else {
1086 inCallOrientationEventListener.disable();
1087 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001088 }
1089
1090 public void setExcludeFromRecents(boolean exclude) {
linyuhc3968e62017-11-20 17:40:50 -08001091 int taskId = getTaskId();
1092
1093 List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks();
1094 for (AppTask task : tasks) {
1095 try {
1096 if (task.getTaskInfo().id == taskId) {
1097 task.setExcludeFromRecents(exclude);
1098 }
1099 } catch (RuntimeException e) {
1100 LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e);
1101 }
1102 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001103 }
1104
Eric Erfanianccca3152017-02-22 16:32:36 -08001105 @Nullable
1106 public FragmentManager getDialpadFragmentManager() {
wangqifd4c9f72018-03-08 18:21:50 -08001107 InCallScreen inCallScreen = getInCallOrRttCallScreen();
Eric Erfanianccca3152017-02-22 16:32:36 -08001108 if (inCallScreen != null) {
1109 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
1110 }
1111 return null;
1112 }
1113
1114 public int getDialpadContainerId() {
wangqifd4c9f72018-03-08 18:21:50 -08001115 return getInCallOrRttCallScreen().getAnswerAndDialpadContainerResourceId();
Eric Erfanianccca3152017-02-22 16:32:36 -08001116 }
1117
1118 @Override
1119 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
1120 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
1121 if (call == null) {
1122 // This is a work around for a bug where we attempt to create a new delegate after the call
1123 // has already been removed. An example of when this can happen is:
1124 // 1. incoming video call in landscape mode
1125 // 2. remote party hangs up
1126 // 3. activity switches from landscape to portrait
1127 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
1128 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
1129 // because this new state is transient and the activity will be destroyed soon.
1130 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
1131 return new AnswerScreenPresenterStub();
1132 } else {
1133 return new AnswerScreenPresenter(
1134 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
1135 }
1136 }
1137
1138 @Override
1139 public InCallScreenDelegate newInCallScreenDelegate() {
1140 return new CallCardPresenter(this);
1141 }
1142
1143 @Override
1144 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
1145 return new CallButtonPresenter(this);
1146 }
1147
1148 @Override
Eric Erfanian90508232017-03-24 09:31:16 -07001149 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
1150 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
1151 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
1152 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
1153 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001154 return new VideoCallPresenter();
1155 }
1156
1157 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001158 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -08001159 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001160 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001161 }
1162
linyuh7b86f562017-11-16 11:24:09 -08001163 public void showToastForWiFiToLteHandover(DialerCall call) {
1164 if (call.hasShownWiFiToLteHandoverToast()) {
1165 return;
1166 }
1167
1168 Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
1169 call.setHasShownWiFiToLteHandoverToast();
Eric Erfanianccca3152017-02-22 16:32:36 -08001170 }
1171
linyuh7b86f562017-11-16 11:24:09 -08001172 public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
1173 if (call.showWifiHandoverAlertAsToast()) {
1174 Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
1175 .show();
1176 return;
1177 }
1178
1179 dismissPendingDialogs();
1180
1181 AlertDialog.Builder builder =
1182 new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
1183
1184 // This allows us to use the theme of the dialog instead of the activity
1185 View dialogCheckBoxView =
1186 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
1187 CheckBox wifiHandoverFailureCheckbox =
1188 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
1189 wifiHandoverFailureCheckbox.setChecked(false);
1190
1191 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
linyuhc3968e62017-11-20 17:40:50 -08001192 errorDialog =
linyuh7b86f562017-11-16 11:24:09 -08001193 builder
1194 .setView(dialogCheckBoxView)
1195 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
1196 .setOnCancelListener(dialogInterface -> onDialogDismissed())
1197 .setPositiveButton(
1198 android.R.string.ok,
1199 (dialogInterface, id) -> {
1200 call.setDoNotShowDialogForHandoffToWifiFailure(
1201 wifiHandoverFailureCheckbox.isChecked());
1202 dialogInterface.cancel();
1203 onDialogDismissed();
1204 })
1205 .setOnDismissListener(dialogInterface -> lock.release())
1206 .create();
linyuh7b86f562017-11-16 11:24:09 -08001207 errorDialog.show();
Eric Erfanianccca3152017-02-22 16:32:36 -08001208 }
1209
linyuh7b86f562017-11-16 11:24:09 -08001210 public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
1211 if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
1212 LogUtil.i(
1213 "InCallActivity.showDialogForInternationalCallOnWifi",
1214 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
1215 return;
1216 }
1217
1218 InternationalCallOnWifiDialogFragment fragment =
1219 InternationalCallOnWifiDialogFragment.newInstance(
linyuhc3968e62017-11-20 17:40:50 -08001220 call.getId(), internationalCallOnWifiCallback);
1221 fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI);
Eric Erfanianc857f902017-05-15 14:05:33 -07001222 }
1223
Eric Erfanian938468d2017-10-24 14:05:52 -07001224 @Override
1225 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
1226 super.onMultiWindowModeChanged(isInMultiWindowMode);
linyuh57b093b2017-11-17 14:32:32 -08001227 updateNavigationBar(isDialpadVisible());
1228 }
1229
linyuhc3968e62017-11-20 17:40:50 -08001230 private void updateNavigationBar(boolean isDialpadVisible) {
linyuh57b093b2017-11-17 14:32:32 -08001231 if (ActivityCompat.isInMultiWindowMode(this)) {
1232 return;
1233 }
1234
1235 View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
1236 if (navigationBarBackground != null) {
1237 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
Eric Erfanian938468d2017-10-24 14:05:52 -07001238 }
1239 }
1240
Eric Erfanianccca3152017-02-22 16:32:36 -08001241 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -07001242 if (this.allowOrientationChange == allowOrientationChange) {
1243 return;
1244 }
1245 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001246 if (!allowOrientationChange) {
1247 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
1248 } else {
1249 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
1250 }
1251 enableInCallOrientationEventListener(allowOrientationChange);
1252 }
1253
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001254 public void hideMainInCallFragment() {
linyuhc3968e62017-11-20 17:40:50 -08001255 LogUtil.enterBlock("InCallActivity.hideMainInCallFragment");
1256 if (getCallCardFragmentVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001257 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1258 hideInCallScreenFragment(transaction);
1259 hideVideoCallScreenFragment(transaction);
1260 transaction.commitAllowingStateLoss();
1261 getSupportFragmentManager().executePendingTransactions();
1262 }
1263 }
1264
1265 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001266 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -08001267 // If the activity's onStart method hasn't been called yet then defer doing any work.
1268 if (!isVisible) {
1269 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001270 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001271 return;
1272 }
1273
1274 // Don't let this be reentrant.
1275 if (isInShowMainInCallFragment) {
1276 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001277 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001278 return;
1279 }
1280
1281 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001282 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
1283 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
wangqi219b8702018-02-13 09:34:41 -08001284 ShouldShowUiResult shouldShowRttUi = getShouldShowRttUi();
Eric Erfanianccca3152017-02-22 16:32:36 -08001285 LogUtil.i(
1286 "InCallActivity.showMainInCallFragment",
wangqi219b8702018-02-13 09:34:41 -08001287 "shouldShowAnswerUi: %b, shouldShowRttUi: %b, shouldShowVideoUi: %b "
1288 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowRttCallScreen: %b, "
1289 + "didShowVideoCallScreen: %b",
Eric Erfanianccca3152017-02-22 16:32:36 -08001290 shouldShowAnswerUi.shouldShow,
wangqi219b8702018-02-13 09:34:41 -08001291 shouldShowRttUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -07001292 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -08001293 didShowAnswerScreen,
1294 didShowInCallScreen,
wangqi219b8702018-02-13 09:34:41 -08001295 didShowRttCallScreen,
Eric Erfanianccca3152017-02-22 16:32:36 -08001296 didShowVideoCallScreen);
1297 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001298 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -08001299
1300 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
wangqi219b8702018-02-13 09:34:41 -08001301 boolean didChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001302 if (shouldShowAnswerUi.shouldShow) {
wangqi219b8702018-02-13 09:34:41 -08001303 didChange = hideInCallScreenFragment(transaction);
1304 didChange |= hideVideoCallScreenFragment(transaction);
1305 didChange |= hideRttCallScreenFragment(transaction);
1306 didChange |= showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001307 } else if (shouldShowVideoUi.shouldShow) {
wangqi219b8702018-02-13 09:34:41 -08001308 didChange = hideInCallScreenFragment(transaction);
1309 didChange |= showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
1310 didChange |= hideRttCallScreenFragment(transaction);
1311 didChange |= hideAnswerScreenFragment(transaction);
1312 } else if (shouldShowRttUi.shouldShow) {
1313 didChange = hideInCallScreenFragment(transaction);
1314 didChange |= hideVideoCallScreenFragment(transaction);
1315 didChange |= hideAnswerScreenFragment(transaction);
1316 didChange |= showRttCallScreenFragment(transaction, shouldShowRttUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001317 } else {
wangqi219b8702018-02-13 09:34:41 -08001318 didChange = showInCallScreenFragment(transaction);
1319 didChange |= hideVideoCallScreenFragment(transaction);
1320 didChange |= hideRttCallScreenFragment(transaction);
1321 didChange |= hideAnswerScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001322 }
1323
wangqi219b8702018-02-13 09:34:41 -08001324 if (didChange) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001325 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -08001326 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001327 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001328 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1329 }
1330 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001331 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001332 }
1333
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001334 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001335 DialerCall call = CallList.getInstance().getIncomingCall();
1336 if (call != null) {
1337 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001338 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001339 }
1340
1341 call = CallList.getInstance().getVideoUpgradeRequestCall();
1342 if (call != null) {
1343 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001344 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001345 }
1346
1347 // Check if we're showing the answer screen and the call is disconnected. If this condition is
1348 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
1349 // the user rejects an incoming call.
1350 call = CallList.getInstance().getFirstCall();
1351 if (call == null) {
1352 call = CallList.getInstance().getBackgroundCall();
1353 }
1354 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
1355 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001356 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001357 }
1358
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001359 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001360 }
1361
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001362 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001363 DialerCall call = CallList.getInstance().getFirstCall();
1364 if (call == null) {
1365 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001366 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001367 }
1368
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001369 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001370 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001371 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001372 }
1373
linyuh8fbecce2017-12-18 13:53:09 -08001374 if (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001375 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001376 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001377 }
1378
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001379 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001380 }
1381
wangqi219b8702018-02-13 09:34:41 -08001382 private static ShouldShowUiResult getShouldShowRttUi() {
1383 DialerCall call = CallList.getInstance().getFirstCall();
1384 if (call == null) {
1385 LogUtil.i("InCallActivity.getShouldShowRttUi", "null call");
1386 return new ShouldShowUiResult(false, null);
1387 }
1388
1389 if (call.isRttCall()) {
1390 LogUtil.i("InCallActivity.getShouldShowRttUi", "found rtt call");
1391 return new ShouldShowUiResult(true, call);
1392 }
1393
1394 if (call.hasSentRttUpgradeRequest()) {
1395 LogUtil.i("InCallActivity.getShouldShowRttUi", "upgrading to rtt");
1396 return new ShouldShowUiResult(true, call);
1397 }
1398
1399 return new ShouldShowUiResult(false, null);
1400 }
1401
Eric Erfanianccca3152017-02-22 16:32:36 -08001402 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
1403 // When rejecting a call the active call can become null in which case we should continue
1404 // showing the answer screen.
1405 if (didShowAnswerScreen && call == null) {
1406 return false;
1407 }
1408
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001409 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
1410
1411 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -08001412
1413 // Check if we're already showing an answer screen for this call.
1414 if (didShowAnswerScreen) {
1415 AnswerScreen answerScreen = getAnswerScreen();
1416 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001417 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -07001418 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
1419 && !answerScreen.isActionTimeout()) {
1420 LogUtil.d(
1421 "InCallActivity.showAnswerScreenFragment",
1422 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -08001423 return false;
1424 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001425 if (answerScreen.isActionTimeout()) {
1426 LogUtil.i(
1427 "InCallActivity.showAnswerScreenFragment",
1428 "answer fragment exists but has been accepted/rejected and timed out");
1429 } else {
1430 LogUtil.i(
1431 "InCallActivity.showAnswerScreenFragment",
1432 "answer fragment exists but arguments do not match");
1433 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001434 hideAnswerScreenFragment(transaction);
1435 }
1436
1437 // Show a new answer screen.
1438 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -07001439 AnswerBindings.createAnswerScreen(
1440 call.getId(),
wangqi219b8702018-02-13 09:34:41 -08001441 call.isRttCall(),
Eric Erfanianfc37b022017-03-21 10:11:17 -07001442 call.isVideoCall(),
1443 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -07001444 call.getVideoTech().isSelfManagedCamera(),
1445 shouldAllowAnswerAndRelease(call),
1446 CallList.getInstance().getBackgroundCall() != null);
linyuhc3968e62017-11-20 17:40:50 -08001447 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001448
1449 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
1450 didShowAnswerScreen = true;
1451 return true;
1452 }
1453
Eric Erfanian90508232017-03-24 09:31:16 -07001454 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
1455 if (CallList.getInstance().getActiveCall() == null) {
1456 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
1457 return false;
1458 }
1459 if (getSystemService(TelephonyManager.class).getPhoneType()
1460 == TelephonyManager.PHONE_TYPE_CDMA) {
1461 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
1462 return false;
1463 }
1464 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
1465 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
1466 return false;
1467 }
linyuhc3968e62017-11-20 17:40:50 -08001468 if (!ConfigProviderBindings.get(this)
1469 .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) {
Eric Erfanian90508232017-03-24 09:31:16 -07001470 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
1471 return false;
1472 }
1473
1474 return true;
1475 }
1476
Eric Erfanianccca3152017-02-22 16:32:36 -08001477 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
1478 if (!didShowAnswerScreen) {
1479 return false;
1480 }
1481 AnswerScreen answerScreen = getAnswerScreen();
1482 if (answerScreen != null) {
1483 transaction.remove(answerScreen.getAnswerScreenFragment());
1484 }
1485
1486 didShowAnswerScreen = false;
1487 return true;
1488 }
1489
1490 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
1491 if (didShowInCallScreen) {
1492 return false;
1493 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001494 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
linyuhc3968e62017-11-20 17:40:50 -08001495 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001496 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1497 didShowInCallScreen = true;
1498 return true;
1499 }
1500
1501 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
1502 if (!didShowInCallScreen) {
1503 return false;
1504 }
1505 InCallScreen inCallScreen = getInCallScreen();
1506 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001507 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -08001508 }
1509 didShowInCallScreen = false;
1510 return true;
1511 }
1512
wangqi219b8702018-02-13 09:34:41 -08001513 private boolean showRttCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
1514 if (didShowRttCallScreen) {
1515 // This shouldn't happen since only one RTT call is allow at same time.
1516 if (!getRttCallScreen().getCallId().equals(call.getId())) {
1517 LogUtil.e("InCallActivity.showRttCallScreenFragment", "RTT call id doesn't match");
1518 }
1519 return false;
1520 }
1521 RttCallScreen rttCallScreen = RttBindings.createRttCallScreen(call.getId());
1522 transaction.add(R.id.main, rttCallScreen.getRttCallScreenFragment(), Tags.RTT_CALL_SCREEN);
1523 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1524 didShowRttCallScreen = true;
1525 return true;
1526 }
1527
1528 private boolean hideRttCallScreenFragment(FragmentTransaction transaction) {
1529 if (!didShowRttCallScreen) {
1530 return false;
1531 }
1532 RttCallScreen rttCallScreen = getRttCallScreen();
1533 if (rttCallScreen != null) {
1534 transaction.remove(rttCallScreen.getRttCallScreenFragment());
1535 }
1536 didShowRttCallScreen = false;
1537 return true;
1538 }
1539
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001540 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001541 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001542 VideoCallScreen videoCallScreen = getVideoCallScreen();
1543 if (videoCallScreen.getCallId().equals(call.getId())) {
1544 return false;
1545 }
1546 LogUtil.i(
1547 "InCallActivity.showVideoCallScreenFragment",
1548 "video call fragment exists but arguments do not match");
1549 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001550 }
1551
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001552 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
1553
Eric Erfanian90508232017-03-24 09:31:16 -07001554 VideoCallScreen videoCallScreen =
1555 VideoBindings.createVideoCallScreen(
1556 call.getId(), call.getVideoTech().shouldUseSurfaceView());
linyuhc3968e62017-11-20 17:40:50 -08001557 transaction.add(
1558 R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001559
1560 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1561 didShowVideoCallScreen = true;
1562 return true;
1563 }
1564
1565 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
1566 if (!didShowVideoCallScreen) {
1567 return false;
1568 }
1569 VideoCallScreen videoCallScreen = getVideoCallScreen();
1570 if (videoCallScreen != null) {
1571 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
1572 }
1573 didShowVideoCallScreen = false;
1574 return true;
1575 }
1576
linyuhc3968e62017-11-20 17:40:50 -08001577 private AnswerScreen getAnswerScreen() {
1578 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001579 }
1580
linyuhc3968e62017-11-20 17:40:50 -08001581 private InCallScreen getInCallScreen() {
1582 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001583 }
1584
linyuhc3968e62017-11-20 17:40:50 -08001585 private VideoCallScreen getVideoCallScreen() {
1586 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001587 }
1588
wangqi219b8702018-02-13 09:34:41 -08001589 private RttCallScreen getRttCallScreen() {
1590 return (RttCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.RTT_CALL_SCREEN);
1591 }
1592
wangqifd4c9f72018-03-08 18:21:50 -08001593 private InCallScreen getInCallOrRttCallScreen() {
1594 InCallScreen inCallScreen = null;
1595 if (didShowInCallScreen) {
1596 inCallScreen = getInCallScreen();
1597 }
1598 if (didShowRttCallScreen) {
1599 inCallScreen = getRttCallScreen();
1600 }
1601 return inCallScreen;
1602 }
1603
Eric Erfanianccca3152017-02-22 16:32:36 -08001604 @Override
1605 public void onPseudoScreenStateChanged(boolean isOn) {
1606 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
1607 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
1608 }
1609
1610 /**
1611 * For some touch related issue, turning off the screen can be faked by drawing a black view over
1612 * the activity. All touch events started when the screen is "off" is rejected.
1613 *
1614 * @see PseudoScreenState
1615 */
1616 @Override
1617 public boolean dispatchTouchEvent(MotionEvent event) {
1618 // Reject any gesture that started when the screen is in the fake off state.
1619 if (touchDownWhenPseudoScreenOff) {
1620 if (event.getAction() == MotionEvent.ACTION_UP) {
1621 touchDownWhenPseudoScreenOff = false;
1622 }
1623 return true;
1624 }
1625 // Reject all touch event when the screen is in the fake off state.
1626 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
1627 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1628 touchDownWhenPseudoScreenOff = true;
1629 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
1630 }
1631 return true;
1632 }
1633 return super.dispatchTouchEvent(event);
1634 }
1635
wangqi219b8702018-02-13 09:34:41 -08001636 @Override
1637 public RttCallScreenDelegate newRttCallScreenDelegate(RttCallScreen videoCallScreen) {
1638 return new RttCallPresenter();
1639 }
1640
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001641 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -08001642 public final boolean shouldShow;
1643 public final DialerCall call;
1644
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001645 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001646 this.shouldShow = shouldShow;
1647 this.call = call;
1648 }
1649 }
linyuhc3968e62017-11-20 17:40:50 -08001650
1651 private static final class IntentExtraNames {
1652 static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent";
1653 static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
1654 static final String SHOW_DIALPAD = "InCallActivity.show_dialpad";
1655 }
1656
1657 private static final class KeysForSavedInstance {
1658 static final String DIALPAD_TEXT = "InCallActivity.dialpad_text";
1659 static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
1660 static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
1661 static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
wangqi153af2f2018-02-15 16:21:49 -08001662 static final String DID_SHOW_RTT_CALL_SCREEN = "did_show_rtt_call_screen";
linyuhc3968e62017-11-20 17:40:50 -08001663 }
1664
1665 /** Request codes for pending intents. */
1666 public static final class PendingIntentRequestCodes {
1667 static final int NON_FULL_SCREEN = 0;
1668 static final int FULL_SCREEN = 1;
1669 static final int BUBBLE = 2;
1670 }
1671
1672 private static final class Tags {
1673 static final String ANSWER_SCREEN = "tag_answer_screen";
1674 static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment";
1675 static final String IN_CALL_SCREEN = "tag_in_call_screen";
1676 static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
1677 static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
1678 static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
wangqi219b8702018-02-13 09:34:41 -08001679 static final String RTT_CALL_SCREEN = "tag_rtt_call_screen";
linyuhc3968e62017-11-20 17:40:50 -08001680 static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
1681 }
1682
1683 private static final class ConfigNames {
1684 static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
1685 }
1686
1687 private static final class InternationalCallOnWifiCallback
1688 implements InternationalCallOnWifiDialogFragment.Callback {
1689 private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName();
1690
1691 @Override
1692 public void continueCall(@NonNull String callId) {
1693 LogUtil.i(TAG, "Continuing call with ID: %s", callId);
1694 }
1695
1696 @Override
1697 public void cancelCall(@NonNull String callId) {
1698 DialerCall call = CallList.getInstance().getCallById(callId);
1699 if (call == null) {
1700 LogUtil.i(TAG, "Call destroyed before the dialog is closed");
1701 return;
1702 }
1703
1704 LogUtil.i(TAG, "Disconnecting international call on WiFi");
1705 call.disconnect();
1706 }
1707 }
1708
1709 private static final class SelectPhoneAccountListener
1710 extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener {
1711 private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName();
1712
twyen73a74c32018-03-07 12:12:24 -08001713 private final Context appContext;
1714
1715 SelectPhoneAccountListener(Context appContext) {
1716 this.appContext = appContext;
1717 }
1718
linyuhc3968e62017-11-20 17:40:50 -08001719 @Override
1720 public void onPhoneAccountSelected(
1721 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
1722 DialerCall call = CallList.getInstance().getCallById(callId);
1723 LogUtil.i(TAG, "Phone account select with call:\n%s", call);
1724
1725 if (call != null) {
twyen73a74c32018-03-07 12:12:24 -08001726 call.phoneAccountSelected(selectedAccountHandle, false);
1727 if (call.getPreferredAccountRecorder() != null) {
1728 call.getPreferredAccountRecorder().record(appContext, selectedAccountHandle, setDefault);
1729 }
linyuhc3968e62017-11-20 17:40:50 -08001730 }
1731 }
1732
1733 @Override
1734 public void onDialogDismissed(String callId) {
1735 DialerCall call = CallList.getInstance().getCallById(callId);
1736 LogUtil.i(TAG, "Disconnecting call:\n%s" + call);
1737
1738 if (call != null) {
1739 call.disconnect();
1740 }
1741 }
1742 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001743}