blob: 8769be5d9e8afd0ac945a267819e42b7c3038342 [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;
linyuhf99f6302017-11-15 11:23:51 -080086import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080087import com.android.incallui.video.bindings.VideoBindings;
88import com.android.incallui.video.protocol.VideoCallScreen;
89import com.android.incallui.video.protocol.VideoCallScreenDelegate;
90import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
linyuhc3968e62017-11-20 17:40:50 -080091import com.google.common.base.Optional;
92import java.lang.annotation.Retention;
93import java.lang.annotation.RetentionPolicy;
94import java.util.ArrayList;
95import java.util.List;
Eric Erfanianccca3152017-02-22 16:32:36 -080096
97/** Version of {@link InCallActivity} that shows the new UI */
98public class InCallActivity extends TransactionSafeFragmentActivity
99 implements AnswerScreenDelegateFactory,
100 InCallScreenDelegateFactory,
101 InCallButtonUiDelegateFactory,
102 VideoCallScreenDelegateFactory,
103 PseudoScreenState.StateChangedListener {
104
linyuhc3968e62017-11-20 17:40:50 -0800105 @Retention(RetentionPolicy.SOURCE)
106 @IntDef({
107 DIALPAD_REQUEST_NONE,
108 DIALPAD_REQUEST_SHOW,
109 DIALPAD_REQUEST_HIDE,
110 })
111 @interface DialpadRequestType {}
Eric Erfanian2ca43182017-08-31 06:57:16 -0700112
linyuhc3968e62017-11-20 17:40:50 -0800113 private static final int DIALPAD_REQUEST_NONE = 1;
114 private static final int DIALPAD_REQUEST_SHOW = 2;
115 private static final int DIALPAD_REQUEST_HIDE = 3;
linyuh57b093b2017-11-17 14:32:32 -0800116
linyuhc3968e62017-11-20 17:40:50 -0800117 private static Optional<Integer> audioRouteForTesting = Optional.absent();
linyuh57b093b2017-11-17 14:32:32 -0800118
linyuhc3968e62017-11-20 17:40:50 -0800119 private final InternationalCallOnWifiCallback internationalCallOnWifiCallback =
120 new InternationalCallOnWifiCallback();
121 private final SelectPhoneAccountListener selectPhoneAccountListener =
122 new SelectPhoneAccountListener();
Eric Erfanianccca3152017-02-22 16:32:36 -0800123
linyuhc3968e62017-11-20 17:40:50 -0800124 private Animation dialpadSlideInAnimation;
125 private Animation dialpadSlideOutAnimation;
126 private Dialog errorDialog;
127 private GradientDrawable backgroundDrawable;
linyuh69a25062017-11-15 16:18:51 -0800128 private InCallOrientationEventListener inCallOrientationEventListener;
linyuhc3968e62017-11-20 17:40:50 -0800129 private View pseudoBlackScreenOverlay;
130 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
131 private String dtmfTextToPrepopulate;
132 private String showPostCharWaitDialogCallId;
133 private String showPostCharWaitDialogChars;
134 private boolean allowOrientationChange;
135 private boolean animateDialpadOnShow;
Eric Erfanianccca3152017-02-22 16:32:36 -0800136 private boolean didShowAnswerScreen;
137 private boolean didShowInCallScreen;
138 private boolean didShowVideoCallScreen;
linyuh9c327da2017-11-14 12:33:48 -0800139 private boolean dismissKeyguard;
Eric Erfanianccca3152017-02-22 16:32:36 -0800140 private boolean isInShowMainInCallFragment;
linyuhc3968e62017-11-20 17:40:50 -0800141 private boolean isRecreating; // whether the activity is going to be recreated
142 private boolean isVisible;
Eric Erfanianccca3152017-02-22 16:32:36 -0800143 private boolean needDismissPendingDialogs;
linyuhc3968e62017-11-20 17:40:50 -0800144 private boolean showPostCharWaitDialogOnResume;
145 private boolean touchDownWhenPseudoScreenOff;
146 private int[] backgroundDrawableColors;
147 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800148
149 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700150 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800151 Intent intent = new Intent(Intent.ACTION_MAIN, null);
152 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
153 intent.setClass(context, InCallActivity.class);
linyuhc3968e62017-11-20 17:40:50 -0800154 if (showDialpad) {
155 intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true);
156 }
157 intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall);
158 intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800159 return intent;
160 }
161
162 @Override
163 protected void onResumeFragments() {
164 super.onResumeFragments();
165 if (needDismissPendingDialogs) {
166 dismissPendingDialogs();
167 }
168 }
169
170 @Override
linyuhc3968e62017-11-20 17:40:50 -0800171 protected void onCreate(Bundle bundle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700172 Trace.beginSection("InCallActivity.onCreate");
linyuhc3968e62017-11-20 17:40:50 -0800173 super.onCreate(bundle);
Eric Erfanianccca3152017-02-22 16:32:36 -0800174
linyuhc3968e62017-11-20 17:40:50 -0800175 if (bundle != null) {
176 didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
177 didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
178 didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800179 }
180
linyuhc3968e62017-11-20 17:40:50 -0800181 setWindowFlags();
182 setContentView(R.layout.incall_screen);
183 internalResolveIntent(getIntent());
184
185 boolean isLandscape =
186 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
187 boolean isRtl = ViewUtil.isRtl();
188 if (isLandscape) {
189 dialpadSlideInAnimation =
190 AnimationUtils.loadAnimation(
191 this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
192 dialpadSlideOutAnimation =
193 AnimationUtils.loadAnimation(
194 this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
195 } else {
196 dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
197 dialpadSlideOutAnimation =
198 AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
199 }
200 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
201 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
202 dialpadSlideOutAnimation.setAnimationListener(
203 new AnimationListenerAdapter() {
204 @Override
205 public void onAnimationEnd(Animation animation) {
206 hideDialpadFragment();
207 }
208 });
209
210 if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
211 // If the dialpad was shown before, set related variables so that it can be shown and
212 // populated with the previous DTMF text during onResume().
213 if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) {
214 boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD);
215 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
216 animateDialpadOnShow = false;
217 }
218 dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT);
219
220 SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
221 (SelectPhoneAccountDialogFragment)
222 getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT);
223 if (selectPhoneAccountDialogFragment != null) {
224 selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener);
225 }
226 }
227
228 InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment =
229 (InternationalCallOnWifiDialogFragment)
230 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
231 if (existingInternationalCallOnWifiDialogFragment != null) {
232 existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback);
233 }
234
linyuh69a25062017-11-15 16:18:51 -0800235 inCallOrientationEventListener = new InCallOrientationEventListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800236
237 getWindow()
238 .getDecorView()
239 .setSystemUiVisibility(
240 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
241
242 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700243 sendBroadcast(CallPendingActivity.getFinishBroadcast());
244 Trace.endSection();
weijiaxuc950a9b2017-11-06 16:39:04 -0800245 Logger.get(this)
246 .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
247 Logger.get(this)
248 .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800249 }
250
linyuhc3968e62017-11-20 17:40:50 -0800251 private void setWindowFlags() {
252 // Allow the activity to be shown when the screen is locked and filter out touch events that are
253 // "too fat".
254 int flags =
255 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
256 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
257
linyuhf79d1cb2017-12-15 17:49:56 -0800258 // When the audio stream is not via Bluetooth, turn on the screen once the activity is shown.
259 // When the audio stream is via Bluetooth, turn on the screen only for an incoming call.
linyuhc3968e62017-11-20 17:40:50 -0800260 final int audioRoute = getAudioRoute();
linyuhf79d1cb2017-12-15 17:49:56 -0800261 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH
262 || CallList.getInstance().getIncomingCall() != null) {
linyuhc3968e62017-11-20 17:40:50 -0800263 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
264 }
265
266 getWindow().addFlags(flags);
267 }
268
269 private static int getAudioRoute() {
270 if (audioRouteForTesting.isPresent()) {
271 return audioRouteForTesting.get();
272 }
273
274 return AudioModeProvider.getInstance().getAudioState().getRoute();
275 }
276
277 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
278 public static void setAudioRouteForTesting(int audioRoute) {
279 audioRouteForTesting = Optional.of(audioRoute);
280 }
281
282 private void internalResolveIntent(Intent intent) {
283 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
284 return;
285 }
286
287 if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) {
288 // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be
289 // initially visible. If the extra is absent, leave the dialpad in its previous state.
290 boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false);
291 relaunchedFromDialer(showDialpad);
292 }
293
294 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
295 if (outgoingCall == null) {
296 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
297 }
298 if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) {
299 intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL);
300
301 // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of
302 // making it (i.e. no valid call capable accounts).
303 // If the version is not MSIM compatible, ignore this code.
304 if (CompatUtils.isMSIMCompatible()
305 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
306 LogUtil.i(
307 "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting");
308 outgoingCall.disconnect();
309 }
310
311 dismissKeyguard(true);
312 }
313
314 if (showPhoneAccountSelectionDialog()) {
315 hideMainInCallFragment();
316 }
317 }
318
319 /**
320 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
321 * be shown on launch.
322 *
323 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
324 * false} to indicate no change should be made to the dialpad visibility.
325 */
326 private void relaunchedFromDialer(boolean showDialpad) {
327 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
328 animateDialpadOnShow = true;
329
330 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
331 // If there's only one line in use, AND it's on hold, then we're sure the user
332 // wants to use the dialpad toward the exact line, so un-hold the holding line.
333 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
334 if (call != null && call.getState() == State.ONHOLD) {
335 call.unhold();
336 }
337 }
338 }
339
340 /**
341 * Show a phone account selection dialog if there is a call waiting for phone account selection.
342 *
343 * @return true if the dialog was shown.
344 */
345 private boolean showPhoneAccountSelectionDialog() {
346 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
347 if (waitingForAccountCall == null) {
348 return false;
349 }
350
351 Bundle extras = waitingForAccountCall.getIntentExtras();
352 List<PhoneAccountHandle> phoneAccountHandles =
353 extras == null
354 ? new ArrayList<>()
355 : extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
356
357 selectPhoneAccountDialogFragment =
358 SelectPhoneAccountDialogFragment.newInstance(
359 R.string.select_phone_account_for_calls,
360 true /* canSetDefault */,
361 0 /* setDefaultResId */,
362 phoneAccountHandles,
363 selectPhoneAccountListener,
364 waitingForAccountCall.getId(),
365 null /* hints */);
366 selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
367 return true;
368 }
369
Eric Erfanianccca3152017-02-22 16:32:36 -0800370 @Override
371 protected void onSaveInstanceState(Bundle out) {
linyuh57b093b2017-11-17 14:32:32 -0800372 LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
373
374 // TODO: DialpadFragment should handle this as part of its own state
linyuhc3968e62017-11-20 17:40:50 -0800375 out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible());
linyuh57b093b2017-11-17 14:32:32 -0800376 DialpadFragment dialpadFragment = getDialpadFragment();
377 if (dialpadFragment != null) {
linyuhc3968e62017-11-20 17:40:50 -0800378 out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText());
linyuh57b093b2017-11-17 14:32:32 -0800379 }
380
linyuhc3968e62017-11-20 17:40:50 -0800381 out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
382 out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
383 out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
linyuh57b093b2017-11-17 14:32:32 -0800384
Eric Erfanianccca3152017-02-22 16:32:36 -0800385 super.onSaveInstanceState(out);
386 isVisible = false;
387 }
388
389 @Override
390 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700391 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800392 super.onStart();
linyuh57b093b2017-11-17 14:32:32 -0800393
Eric Erfanianccca3152017-02-22 16:32:36 -0800394 isVisible = true;
395 showMainInCallFragment();
linyuh57b093b2017-11-17 14:32:32 -0800396
397 InCallPresenter.getInstance().setActivity(this);
398 enableInCallOrientationEventListener(
399 getRequestedOrientation()
400 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
401 InCallPresenter.getInstance().onActivityStarted();
402
yueg10f6e822018-01-17 15:32:18 -0800403 if (!isRecreating) {
404 InCallPresenter.getInstance().onUiShowing(true);
405 }
406
Eric Erfanianccca3152017-02-22 16:32:36 -0800407 if (ActivityCompat.isInMultiWindowMode(this)
408 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
409 // Hide the dialpad because there may not be enough room
410 showDialpadFragment(false, false);
411 }
linyuh57b093b2017-11-17 14:32:32 -0800412
Eric Erfanian2ca43182017-08-31 06:57:16 -0700413 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800414 }
415
416 @Override
417 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700418 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 super.onResume();
linyuhc3968e62017-11-20 17:40:50 -0800420
421 if (!InCallPresenter.getInstance().isReadyForTearDown()) {
422 updateTaskDescription();
yueg10f6e822018-01-17 15:32:18 -0800423 InCallPresenter.getInstance().updateNotification();
linyuhc3968e62017-11-20 17:40:50 -0800424 }
425
426 // If there is a pending request to show or hide the dialpad, handle that now.
427 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
428 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
429 // Exit fullscreen so that the user has access to the dialpad hide/show button.
430 // This is important when showing the dialpad from within dialer.
431 InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */);
432
433 showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
434 animateDialpadOnShow = false;
435
436 DialpadFragment dialpadFragment = getDialpadFragment();
437 if (dialpadFragment != null) {
438 dialpadFragment.setDtmfText(dtmfTextToPrepopulate);
439 dtmfTextToPrepopulate = null;
440 }
441 } else {
442 LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad");
443 if (getDialpadFragment() != null) {
444 showDialpadFragment(false /* show */, false /* animate */);
445 }
446 }
447 showDialpadRequest = DIALPAD_REQUEST_NONE;
448 }
449 updateNavigationBar(isDialpadVisible());
450
451 if (showPostCharWaitDialogOnResume) {
452 showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
453 }
454
455 CallList.getInstance()
456 .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false));
457
Eric Erfanianccca3152017-02-22 16:32:36 -0800458 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
459 pseudoScreenState.addListener(this);
460 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700461 Trace.endSection();
weijiaxu650e7cc2017-10-31 12:38:54 -0700462 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
463 ThreadUtil.postDelayedOnUiThread(
weijiaxuc950a9b2017-11-06 16:39:04 -0800464 () ->
465 Logger.get(this)
466 .logRecordMemory(LoggingBindings.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME),
weijiaxu650e7cc2017-10-31 12:38:54 -0700467 1000);
Eric Erfanianccca3152017-02-22 16:32:36 -0800468 }
469
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 @Override
471 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700472 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 super.onPause();
linyuh57b093b2017-11-17 14:32:32 -0800474
475 DialpadFragment dialpadFragment = getDialpadFragment();
476 if (dialpadFragment != null) {
477 dialpadFragment.onDialerKeyUp(null);
478 }
479
yueg10f6e822018-01-17 15:32:18 -0800480 InCallPresenter.getInstance().updateNotification();
linyuh57b093b2017-11-17 14:32:32 -0800481
Eric Erfanianccca3152017-02-22 16:32:36 -0800482 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700483 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800484 }
485
486 @Override
487 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700488 Trace.beginSection("InCallActivity.onStop");
wangqi4d705e52017-09-28 12:23:35 -0700489 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800490 super.onStop();
linyuh57b093b2017-11-17 14:32:32 -0800491
492 // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
493 // user presses the home button).
494 // Without this the pending call will get stuck on phone account selection and new calls can't
495 // be created.
496 // Skip this when the screen is locked since the activity may complete its current life cycle
497 // and restart.
linyuhc3968e62017-11-20 17:40:50 -0800498 if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
linyuh57b093b2017-11-17 14:32:32 -0800499 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
500 if (waitingForAccountCall != null) {
501 waitingForAccountCall.disconnect();
502 }
503 }
504
505 enableInCallOrientationEventListener(false);
506 InCallPresenter.getInstance().updateIsChangingConfigurations();
507 InCallPresenter.getInstance().onActivityStopped();
linyuhc3968e62017-11-20 17:40:50 -0800508 if (!isRecreating) {
yueg10f6e822018-01-17 15:32:18 -0800509 InCallPresenter.getInstance().onUiShowing(false);
linyuh57b093b2017-11-17 14:32:32 -0800510 if (errorDialog != null) {
511 errorDialog.dismiss();
512 }
513 }
514
yueg10f6e822018-01-17 15:32:18 -0800515 if (isFinishing()) {
516 InCallPresenter.getInstance().unsetActivity(this);
517 }
518
Eric Erfanian2ca43182017-08-31 06:57:16 -0700519 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800520 }
521
522 @Override
523 protected void onDestroy() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700524 Trace.beginSection("InCallActivity.onDestroy");
Eric Erfanianccca3152017-02-22 16:32:36 -0800525 super.onDestroy();
linyuh57b093b2017-11-17 14:32:32 -0800526
527 InCallPresenter.getInstance().unsetActivity(this);
528 InCallPresenter.getInstance().updateIsChangingConfigurations();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700529 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800530 }
531
532 @Override
533 public void finish() {
534 if (shouldCloseActivityOnFinish()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700535 // When user select incall ui from recents after the call is disconnected, it tries to launch
536 // a new InCallActivity but InCallPresenter is already teared down at this point, which causes
537 // crash.
538 // By calling finishAndRemoveTask() instead of finish() the task associated with
539 // InCallActivity is cleared completely. So system won't try to create a new InCallActivity in
540 // this case.
541 //
542 // Calling finish won't clear the task and normally when an activity finishes it shouldn't
543 // clear the task since there could be parent activity in the same task that's still alive.
544 // But InCallActivity is special since it's singleInstance which means it's root activity and
545 // only instance of activity in the task. So it should be safe to also remove task when
546 // finishing.
547 // It's also necessary in the sense of it's excluded from recents. So whenever the activity
548 // finishes, the task should also be removed since it doesn't make sense to go back to it in
549 // anyway anymore.
550 super.finishAndRemoveTask();
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 }
552 }
553
554 private boolean shouldCloseActivityOnFinish() {
linyuhc3968e62017-11-20 17:40:50 -0800555 if (!isVisible) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 LogUtil.i(
557 "InCallActivity.shouldCloseActivityOnFinish",
558 "allowing activity to be closed because it's not visible");
559 return true;
560 }
561
twyen8efb4952017-10-06 16:35:54 -0700562 if (InCallPresenter.getInstance().isInCallUiLocked()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 LogUtil.i(
564 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700565 "in call ui is locked, not closing activity");
Eric Erfanianccca3152017-02-22 16:32:36 -0800566 return false;
567 }
568
569 LogUtil.i(
570 "InCallActivity.shouldCloseActivityOnFinish",
twyen8efb4952017-10-06 16:35:54 -0700571 "activity is visible and has no locks, allowing activity to close");
Eric Erfanianccca3152017-02-22 16:32:36 -0800572 return true;
573 }
574
575 @Override
576 protected void onNewIntent(Intent intent) {
linyuhc3968e62017-11-20 17:40:50 -0800577 LogUtil.enterBlock("InCallActivity.onNewIntent");
Eric Erfanianccca3152017-02-22 16:32:36 -0800578
579 // If the screen is off, we need to make sure it gets turned on for incoming calls.
580 // This normally works just fine thanks to FLAG_TURN_SCREEN_ON but that only works
581 // when the activity is first created. Therefore, to ensure the screen is turned on
582 // for the call waiting case, we recreate() the current activity. There should be no jank from
583 // this since the screen is already off and will remain so until our new activity is up.
linyuhc3968e62017-11-20 17:40:50 -0800584 if (!isVisible) {
585 onNewIntent(intent, true /* isRecreating */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800586 LogUtil.i("InCallActivity.onNewIntent", "Restarting InCallActivity to force screen on.");
587 recreate();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700588 } else {
linyuhc3968e62017-11-20 17:40:50 -0800589 onNewIntent(intent, false /* isRecreating */);
590 }
591 }
592
yuega3305352018-01-09 11:02:47 -0800593 @VisibleForTesting
594 void onNewIntent(Intent intent, boolean isRecreating) {
linyuhc3968e62017-11-20 17:40:50 -0800595 this.isRecreating = isRecreating;
596
597 // We're being re-launched with a new Intent. Since it's possible for a single InCallActivity
598 // instance to persist indefinitely (even if we finish() ourselves), this sequence can
599 // happen any time the InCallActivity needs to be displayed.
600
601 // Stash away the new intent so that we can get it in the future by calling getIntent().
602 // Otherwise getIntent() will return the original Intent from when we first got created.
603 setIntent(intent);
604
605 // Activities are always paused before receiving a new intent, so we can count on our onResume()
606 // method being called next.
607
608 // Just like in onCreate(), handle the intent.
609 // Skip if InCallActivity is going to be recreated since this will be called in onCreate().
610 if (!isRecreating) {
611 internalResolveIntent(intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800612 }
613 }
614
615 @Override
616 public void onBackPressed() {
linyuh57b093b2017-11-17 14:32:32 -0800617 LogUtil.enterBlock("InCallActivity.onBackPressed");
618
linyuhc3968e62017-11-20 17:40:50 -0800619 if (!isVisible) {
linyuh57b093b2017-11-17 14:32:32 -0800620 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 }
linyuh57b093b2017-11-17 14:32:32 -0800622
623 if (!getCallCardFragmentVisible()) {
624 return;
625 }
626
627 DialpadFragment dialpadFragment = getDialpadFragment();
628 if (dialpadFragment != null && dialpadFragment.isVisible()) {
629 showDialpadFragment(false /* show */, true /* animate */);
630 return;
631 }
632
633 if (CallList.getInstance().getIncomingCall() != null) {
634 LogUtil.i(
635 "InCallActivity.onBackPressed",
636 "Ignore the press of the back key when an incoming call is ringing");
637 return;
638 }
639
640 // Nothing special to do. Fall back to the default behavior.
641 super.onBackPressed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800642 }
643
644 @Override
645 public boolean onOptionsItemSelected(MenuItem item) {
646 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
647 if (item.getItemId() == android.R.id.home) {
648 onBackPressed();
649 return true;
650 }
651 return super.onOptionsItemSelected(item);
652 }
653
654 @Override
655 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800656 DialpadFragment dialpadFragment = getDialpadFragment();
657 if (dialpadFragment != null
658 && dialpadFragment.isVisible()
659 && dialpadFragment.onDialerKeyUp(event)) {
660 return true;
661 }
662
663 if (keyCode == KeyEvent.KEYCODE_CALL) {
664 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
665 return true;
666 }
667
668 return super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800669 }
670
671 @Override
672 public boolean onKeyDown(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800673 switch (keyCode) {
674 case KeyEvent.KEYCODE_CALL:
675 if (!InCallPresenter.getInstance().handleCallKey()) {
676 LogUtil.e(
677 "InCallActivity.onKeyDown",
678 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
679 }
680 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
681 return true;
682
683 // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
684 // is exactly what's needed, namely
685 // (1) "hang up" if there's an active call, or
686 // (2) "don't answer" if there's an incoming call.
687 // (See PhoneWindowManager for implementation details.)
688
689 case KeyEvent.KEYCODE_CAMERA:
690 // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
691 return true;
692
693 case KeyEvent.KEYCODE_VOLUME_UP:
694 case KeyEvent.KEYCODE_VOLUME_DOWN:
695 case KeyEvent.KEYCODE_VOLUME_MUTE:
696 // Ringer silencing handled by PhoneWindowManager.
697 break;
698
699 case KeyEvent.KEYCODE_MUTE:
700 TelecomAdapter.getInstance()
701 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
702 return true;
703
704 case KeyEvent.KEYCODE_SLASH:
705 // When verbose logging is enabled, dump the view for debugging/testing purposes.
706 if (LogUtil.isVerboseEnabled()) {
707 View decorView = getWindow().getDecorView();
708 LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
709 return true;
710 }
711 break;
712
713 case KeyEvent.KEYCODE_EQUALS:
714 break;
715
716 default: // fall out
717 }
718
719 // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
720 // in DTMF (Dual-tone multi-frequency signaling) code.
721 DialpadFragment dialpadFragment = getDialpadFragment();
722 if (dialpadFragment != null
723 && dialpadFragment.isVisible()
724 && dialpadFragment.onDialerKeyDown(event)) {
725 return true;
726 }
727
728 return super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800729 }
730
731 public boolean isInCallScreenAnimating() {
732 return false;
733 }
734
735 public void showConferenceFragment(boolean show) {
736 if (show) {
737 startActivity(new Intent(this, ManageConferenceActivity.class));
738 }
739 }
740
linyuhc3968e62017-11-20 17:40:50 -0800741 public void showDialpadFragment(boolean show, boolean animate) {
742 if (show == isDialpadVisible()) {
743 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800744 }
linyuhc3968e62017-11-20 17:40:50 -0800745
746 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
747 if (dialpadFragmentManager == null) {
748 LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager");
749 return;
750 }
751
752 if (!animate) {
753 if (show) {
754 showDialpadFragment();
755 } else {
756 hideDialpadFragment();
757 }
758 } else {
759 if (show) {
760 showDialpadFragment();
761 getDialpadFragment().animateShowDialpad();
762 }
763 getDialpadFragment()
764 .getView()
765 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
766 }
767
768 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
769 if (sensor != null) {
770 sensor.onDialpadVisible(show);
771 }
772 showDialpadRequest = DIALPAD_REQUEST_NONE;
773
774 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
775 // repositions itself.
776 getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
777 }
778
779 private void showDialpadFragment() {
780 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
781 if (dialpadFragmentManager == null) {
782 return;
783 }
784
785 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
786 DialpadFragment dialpadFragment = getDialpadFragment();
787 if (dialpadFragment == null) {
788 transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT);
789 } else {
790 transaction.show(dialpadFragment);
calderwoodrad5883872017-12-12 15:29:12 -0800791 dialpadFragment.setUserVisibleHint(true);
linyuhc3968e62017-11-20 17:40:50 -0800792 }
793 transaction.commitAllowingStateLoss();
794 dialpadFragmentManager.executePendingTransactions();
795
796 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this);
797 updateNavigationBar(true /* isDialpadVisible */);
798 }
799
800 private void hideDialpadFragment() {
801 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
802 if (dialpadFragmentManager == null) {
803 return;
804 }
805
calderwoodrad5883872017-12-12 15:29:12 -0800806 DialpadFragment dialpadFragment = getDialpadFragment();
linyuhc3968e62017-11-20 17:40:50 -0800807 if (dialpadFragment != null) {
808 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
809 transaction.hide(dialpadFragment);
810 transaction.commitAllowingStateLoss();
811 dialpadFragmentManager.executePendingTransactions();
calderwoodrad5883872017-12-12 15:29:12 -0800812 dialpadFragment.setUserVisibleHint(false);
linyuhc3968e62017-11-20 17:40:50 -0800813 }
814 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800815 }
816
817 public boolean isDialpadVisible() {
linyuh69a25062017-11-15 16:18:51 -0800818 DialpadFragment dialpadFragment = getDialpadFragment();
calderwoodraa584bcd2018-01-24 12:19:56 -0800819 return dialpadFragment != null
820 && dialpadFragment.isAdded()
821 && !dialpadFragment.isHidden()
822 && dialpadFragment.getView() != null
823 && dialpadFragment.getUserVisibleHint();
linyuh69a25062017-11-15 16:18:51 -0800824 }
825
linyuhc3968e62017-11-20 17:40:50 -0800826 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
linyuh69a25062017-11-15 16:18:51 -0800827 @Nullable
linyuhc3968e62017-11-20 17:40:50 -0800828 private DialpadFragment getDialpadFragment() {
linyuh69a25062017-11-15 16:18:51 -0800829 FragmentManager fragmentManager = getDialpadFragmentManager();
830 if (fragmentManager == null) {
831 return null;
832 }
linyuhc3968e62017-11-20 17:40:50 -0800833 return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 }
835
836 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh57b093b2017-11-17 14:32:32 -0800837 updateTaskDescription();
838
839 if (newForegroundCall == null || !didShowAnswerScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800840 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
linyuh57b093b2017-11-17 14:32:32 -0800841 updateWindowBackgroundColor(0 /* progress */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800842 }
843 }
844
linyuhc3968e62017-11-20 17:40:50 -0800845 private void updateTaskDescription() {
linyuh57b093b2017-11-17 14:32:32 -0800846 int color =
847 getResources().getBoolean(R.bool.is_layout_landscape)
848 ? ResourcesCompat.getColor(
849 getResources(), R.color.statusbar_background_color, getTheme())
850 : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
851 setTaskDescription(
852 new TaskDescription(
853 getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
854 }
855
Eric Erfanianccca3152017-02-22 16:32:36 -0800856 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
857 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
858 @ColorInt int top;
859 @ColorInt int middle;
860 @ColorInt int bottom;
861 @ColorInt int gray = 0x66000000;
862
863 if (ActivityCompat.isInMultiWindowMode(this)) {
864 top = themeColorManager.getBackgroundColorSolid();
865 middle = themeColorManager.getBackgroundColorSolid();
866 bottom = themeColorManager.getBackgroundColorSolid();
867 } else {
868 top = themeColorManager.getBackgroundColorTop();
869 middle = themeColorManager.getBackgroundColorMiddle();
870 bottom = themeColorManager.getBackgroundColorBottom();
871 }
872
873 if (progress < 0) {
874 float correctedProgress = Math.abs(progress);
875 top = ColorUtils.blendARGB(top, gray, correctedProgress);
876 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
877 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
878 }
879
880 boolean backgroundDirty = false;
881 if (backgroundDrawable == null) {
882 backgroundDrawableColors = new int[] {top, middle, bottom};
883 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
884 backgroundDirty = true;
885 } else {
886 if (backgroundDrawableColors[0] != top) {
887 backgroundDrawableColors[0] = top;
888 backgroundDirty = true;
889 }
890 if (backgroundDrawableColors[1] != middle) {
891 backgroundDrawableColors[1] = middle;
892 backgroundDirty = true;
893 }
894 if (backgroundDrawableColors[2] != bottom) {
895 backgroundDrawableColors[2] = bottom;
896 backgroundDirty = true;
897 }
898 if (backgroundDirty) {
899 backgroundDrawable.setColors(backgroundDrawableColors);
900 }
901 }
902
903 if (backgroundDirty) {
904 getWindow().setBackgroundDrawable(backgroundDrawable);
905 }
906 }
907
908 public boolean isVisible() {
909 return isVisible;
910 }
911
912 public boolean getCallCardFragmentVisible() {
913 return didShowInCallScreen || didShowVideoCallScreen;
914 }
915
916 public void dismissKeyguard(boolean dismiss) {
linyuh9c327da2017-11-14 12:33:48 -0800917 if (dismissKeyguard == dismiss) {
918 return;
919 }
920
921 dismissKeyguard = dismiss;
922 if (dismiss) {
923 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
924 } else {
925 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
926 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800927 }
928
linyuhc3968e62017-11-20 17:40:50 -0800929 public void showDialogForPostCharWait(String callId, String chars) {
930 if (isVisible) {
931 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
932 fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT);
933
934 showPostCharWaitDialogOnResume = false;
935 showPostCharWaitDialogCallId = null;
936 showPostCharWaitDialogChars = null;
937 } else {
938 showPostCharWaitDialogOnResume = true;
939 showPostCharWaitDialogCallId = callId;
940 showPostCharWaitDialogChars = chars;
941 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800942 }
943
linyuh7b86f562017-11-16 11:24:09 -0800944 public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
945 LogUtil.i(
946 "InCallActivity.showDialogOrToastForDisconnectedCall",
947 "disconnect cause: %s",
948 disconnectMessage);
949
950 if (disconnectMessage.dialog == null || isFinishing()) {
951 return;
952 }
953
954 dismissPendingDialogs();
955
956 // Show a toast if the app is in background when a dialog can't be visible.
957 if (!isVisible()) {
958 Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
959 .show();
960 return;
961 }
962
963 // Show the dialog.
linyuhc3968e62017-11-20 17:40:50 -0800964 errorDialog = disconnectMessage.dialog;
linyuh7b86f562017-11-16 11:24:09 -0800965 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
966 disconnectMessage.dialog.setOnDismissListener(
967 dialogInterface -> {
968 lock.release();
969 onDialogDismissed();
970 });
971 disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
972 disconnectMessage.dialog.show();
973 }
974
975 private void onDialogDismissed() {
linyuhc3968e62017-11-20 17:40:50 -0800976 errorDialog = null;
linyuh7b86f562017-11-16 11:24:09 -0800977 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800978 }
979
980 public void dismissPendingDialogs() {
linyuhc3968e62017-11-20 17:40:50 -0800981 LogUtil.enterBlock("InCallActivity.dismissPendingDialogs");
linyuhf99f6302017-11-15 11:23:51 -0800982
983 if (!isVisible) {
984 // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
985 // been called.
Eric Erfanianccca3152017-02-22 16:32:36 -0800986 LogUtil.i(
987 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
988 needDismissPendingDialogs = true;
linyuhf99f6302017-11-15 11:23:51 -0800989 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800990 }
linyuhf99f6302017-11-15 11:23:51 -0800991
992 // Dismiss the error dialog
linyuhf99f6302017-11-15 11:23:51 -0800993 if (errorDialog != null) {
994 errorDialog.dismiss();
linyuhc3968e62017-11-20 17:40:50 -0800995 errorDialog = null;
linyuhf99f6302017-11-15 11:23:51 -0800996 }
997
998 // Dismiss the phone account selection dialog
linyuhf99f6302017-11-15 11:23:51 -0800999 if (selectPhoneAccountDialogFragment != null) {
1000 selectPhoneAccountDialogFragment.dismiss();
linyuhc3968e62017-11-20 17:40:50 -08001001 selectPhoneAccountDialogFragment = null;
linyuhf99f6302017-11-15 11:23:51 -08001002 }
1003
1004 // Dismiss the dialog for international call on WiFi
1005 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
1006 (InternationalCallOnWifiDialogFragment)
linyuhc3968e62017-11-20 17:40:50 -08001007 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
linyuhf99f6302017-11-15 11:23:51 -08001008 if (internationalCallOnWifiFragment != null) {
1009 internationalCallOnWifiFragment.dismiss();
1010 }
1011
1012 // Dismiss the answer screen
1013 AnswerScreen answerScreen = getAnswerScreen();
1014 if (answerScreen != null) {
1015 answerScreen.dismissPendingDialogs();
1016 }
1017
1018 needDismissPendingDialogs = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001019 }
1020
linyuhc3968e62017-11-20 17:40:50 -08001021 private void enableInCallOrientationEventListener(boolean enable) {
linyuh69a25062017-11-15 16:18:51 -08001022 if (enable) {
1023 inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
1024 } else {
1025 inCallOrientationEventListener.disable();
1026 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001027 }
1028
1029 public void setExcludeFromRecents(boolean exclude) {
linyuhc3968e62017-11-20 17:40:50 -08001030 int taskId = getTaskId();
1031
1032 List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks();
1033 for (AppTask task : tasks) {
1034 try {
1035 if (task.getTaskInfo().id == taskId) {
1036 task.setExcludeFromRecents(exclude);
1037 }
1038 } catch (RuntimeException e) {
1039 LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e);
1040 }
1041 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001042 }
1043
Eric Erfanianccca3152017-02-22 16:32:36 -08001044 @Nullable
1045 public FragmentManager getDialpadFragmentManager() {
1046 InCallScreen inCallScreen = getInCallScreen();
1047 if (inCallScreen != null) {
1048 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
1049 }
1050 return null;
1051 }
1052
1053 public int getDialpadContainerId() {
1054 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
1055 }
1056
1057 @Override
1058 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
1059 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
1060 if (call == null) {
1061 // This is a work around for a bug where we attempt to create a new delegate after the call
1062 // has already been removed. An example of when this can happen is:
1063 // 1. incoming video call in landscape mode
1064 // 2. remote party hangs up
1065 // 3. activity switches from landscape to portrait
1066 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
1067 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
1068 // because this new state is transient and the activity will be destroyed soon.
1069 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
1070 return new AnswerScreenPresenterStub();
1071 } else {
1072 return new AnswerScreenPresenter(
1073 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
1074 }
1075 }
1076
1077 @Override
1078 public InCallScreenDelegate newInCallScreenDelegate() {
1079 return new CallCardPresenter(this);
1080 }
1081
1082 @Override
1083 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
1084 return new CallButtonPresenter(this);
1085 }
1086
1087 @Override
Eric Erfanian90508232017-03-24 09:31:16 -07001088 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
1089 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
1090 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
1091 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
1092 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001093 return new VideoCallPresenter();
1094 }
1095
1096 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001097 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -08001098 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001099 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001100 }
1101
linyuh7b86f562017-11-16 11:24:09 -08001102 public void showToastForWiFiToLteHandover(DialerCall call) {
1103 if (call.hasShownWiFiToLteHandoverToast()) {
1104 return;
1105 }
1106
1107 Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
1108 call.setHasShownWiFiToLteHandoverToast();
Eric Erfanianccca3152017-02-22 16:32:36 -08001109 }
1110
linyuh7b86f562017-11-16 11:24:09 -08001111 public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
1112 if (call.showWifiHandoverAlertAsToast()) {
1113 Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
1114 .show();
1115 return;
1116 }
1117
1118 dismissPendingDialogs();
1119
1120 AlertDialog.Builder builder =
1121 new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
1122
1123 // This allows us to use the theme of the dialog instead of the activity
1124 View dialogCheckBoxView =
1125 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
1126 CheckBox wifiHandoverFailureCheckbox =
1127 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
1128 wifiHandoverFailureCheckbox.setChecked(false);
1129
1130 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
linyuhc3968e62017-11-20 17:40:50 -08001131 errorDialog =
linyuh7b86f562017-11-16 11:24:09 -08001132 builder
1133 .setView(dialogCheckBoxView)
1134 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
1135 .setOnCancelListener(dialogInterface -> onDialogDismissed())
1136 .setPositiveButton(
1137 android.R.string.ok,
1138 (dialogInterface, id) -> {
1139 call.setDoNotShowDialogForHandoffToWifiFailure(
1140 wifiHandoverFailureCheckbox.isChecked());
1141 dialogInterface.cancel();
1142 onDialogDismissed();
1143 })
1144 .setOnDismissListener(dialogInterface -> lock.release())
1145 .create();
linyuh7b86f562017-11-16 11:24:09 -08001146 errorDialog.show();
Eric Erfanianccca3152017-02-22 16:32:36 -08001147 }
1148
linyuh7b86f562017-11-16 11:24:09 -08001149 public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
1150 if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
1151 LogUtil.i(
1152 "InCallActivity.showDialogForInternationalCallOnWifi",
1153 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
1154 return;
1155 }
1156
1157 InternationalCallOnWifiDialogFragment fragment =
1158 InternationalCallOnWifiDialogFragment.newInstance(
linyuhc3968e62017-11-20 17:40:50 -08001159 call.getId(), internationalCallOnWifiCallback);
1160 fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI);
Eric Erfanianc857f902017-05-15 14:05:33 -07001161 }
1162
Eric Erfanian938468d2017-10-24 14:05:52 -07001163 @Override
1164 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
1165 super.onMultiWindowModeChanged(isInMultiWindowMode);
linyuh57b093b2017-11-17 14:32:32 -08001166 updateNavigationBar(isDialpadVisible());
1167 }
1168
linyuhc3968e62017-11-20 17:40:50 -08001169 private void updateNavigationBar(boolean isDialpadVisible) {
linyuh57b093b2017-11-17 14:32:32 -08001170 if (ActivityCompat.isInMultiWindowMode(this)) {
1171 return;
1172 }
1173
1174 View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
1175 if (navigationBarBackground != null) {
1176 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
Eric Erfanian938468d2017-10-24 14:05:52 -07001177 }
1178 }
1179
Eric Erfanianccca3152017-02-22 16:32:36 -08001180 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -07001181 if (this.allowOrientationChange == allowOrientationChange) {
1182 return;
1183 }
1184 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001185 if (!allowOrientationChange) {
1186 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
1187 } else {
1188 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
1189 }
1190 enableInCallOrientationEventListener(allowOrientationChange);
1191 }
1192
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001193 public void hideMainInCallFragment() {
linyuhc3968e62017-11-20 17:40:50 -08001194 LogUtil.enterBlock("InCallActivity.hideMainInCallFragment");
1195 if (getCallCardFragmentVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001196 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1197 hideInCallScreenFragment(transaction);
1198 hideVideoCallScreenFragment(transaction);
1199 transaction.commitAllowingStateLoss();
1200 getSupportFragmentManager().executePendingTransactions();
1201 }
1202 }
1203
1204 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001205 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -08001206 // If the activity's onStart method hasn't been called yet then defer doing any work.
1207 if (!isVisible) {
1208 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001209 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001210 return;
1211 }
1212
1213 // Don't let this be reentrant.
1214 if (isInShowMainInCallFragment) {
1215 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001216 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001217 return;
1218 }
1219
1220 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001221 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
1222 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
Eric Erfanianccca3152017-02-22 16:32:36 -08001223 LogUtil.i(
1224 "InCallActivity.showMainInCallFragment",
1225 "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
1226 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
1227 shouldShowAnswerUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -07001228 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -08001229 didShowAnswerScreen,
1230 didShowInCallScreen,
1231 didShowVideoCallScreen);
1232 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001233 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -08001234
1235 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1236 boolean didChangeInCall;
1237 boolean didChangeVideo;
1238 boolean didChangeAnswer;
1239 if (shouldShowAnswerUi.shouldShow) {
1240 didChangeInCall = hideInCallScreenFragment(transaction);
1241 didChangeVideo = hideVideoCallScreenFragment(transaction);
1242 didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001243 } else if (shouldShowVideoUi.shouldShow) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001244 didChangeInCall = hideInCallScreenFragment(transaction);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001245 didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001246 didChangeAnswer = hideAnswerScreenFragment(transaction);
1247 } else {
1248 didChangeInCall = showInCallScreenFragment(transaction);
1249 didChangeVideo = hideVideoCallScreenFragment(transaction);
1250 didChangeAnswer = hideAnswerScreenFragment(transaction);
1251 }
1252
1253 if (didChangeInCall || didChangeVideo || didChangeAnswer) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001254 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -08001255 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001256 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001257 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1258 }
1259 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001260 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001261 }
1262
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001263 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001264 DialerCall call = CallList.getInstance().getIncomingCall();
1265 if (call != null) {
1266 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001267 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001268 }
1269
1270 call = CallList.getInstance().getVideoUpgradeRequestCall();
1271 if (call != null) {
1272 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001273 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001274 }
1275
1276 // Check if we're showing the answer screen and the call is disconnected. If this condition is
1277 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
1278 // the user rejects an incoming call.
1279 call = CallList.getInstance().getFirstCall();
1280 if (call == null) {
1281 call = CallList.getInstance().getBackgroundCall();
1282 }
1283 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
1284 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001285 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001286 }
1287
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001288 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001289 }
1290
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001291 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001292 DialerCall call = CallList.getInstance().getFirstCall();
1293 if (call == null) {
1294 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001295 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001296 }
1297
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001298 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001299 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001300 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001301 }
1302
linyuh8fbecce2017-12-18 13:53:09 -08001303 if (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001304 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001305 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001306 }
1307
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001308 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001309 }
1310
1311 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
1312 // When rejecting a call the active call can become null in which case we should continue
1313 // showing the answer screen.
1314 if (didShowAnswerScreen && call == null) {
1315 return false;
1316 }
1317
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001318 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
1319
1320 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -08001321
1322 // Check if we're already showing an answer screen for this call.
1323 if (didShowAnswerScreen) {
1324 AnswerScreen answerScreen = getAnswerScreen();
1325 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001326 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -07001327 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
1328 && !answerScreen.isActionTimeout()) {
1329 LogUtil.d(
1330 "InCallActivity.showAnswerScreenFragment",
1331 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -08001332 return false;
1333 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001334 if (answerScreen.isActionTimeout()) {
1335 LogUtil.i(
1336 "InCallActivity.showAnswerScreenFragment",
1337 "answer fragment exists but has been accepted/rejected and timed out");
1338 } else {
1339 LogUtil.i(
1340 "InCallActivity.showAnswerScreenFragment",
1341 "answer fragment exists but arguments do not match");
1342 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001343 hideAnswerScreenFragment(transaction);
1344 }
1345
1346 // Show a new answer screen.
1347 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -07001348 AnswerBindings.createAnswerScreen(
1349 call.getId(),
1350 call.isVideoCall(),
1351 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -07001352 call.getVideoTech().isSelfManagedCamera(),
1353 shouldAllowAnswerAndRelease(call),
1354 CallList.getInstance().getBackgroundCall() != null);
linyuhc3968e62017-11-20 17:40:50 -08001355 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001356
1357 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
1358 didShowAnswerScreen = true;
1359 return true;
1360 }
1361
Eric Erfanian90508232017-03-24 09:31:16 -07001362 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
1363 if (CallList.getInstance().getActiveCall() == null) {
1364 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
1365 return false;
1366 }
1367 if (getSystemService(TelephonyManager.class).getPhoneType()
1368 == TelephonyManager.PHONE_TYPE_CDMA) {
1369 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
1370 return false;
1371 }
1372 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
1373 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
1374 return false;
1375 }
linyuhc3968e62017-11-20 17:40:50 -08001376 if (!ConfigProviderBindings.get(this)
1377 .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) {
Eric Erfanian90508232017-03-24 09:31:16 -07001378 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
1379 return false;
1380 }
1381
1382 return true;
1383 }
1384
Eric Erfanianccca3152017-02-22 16:32:36 -08001385 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
1386 if (!didShowAnswerScreen) {
1387 return false;
1388 }
1389 AnswerScreen answerScreen = getAnswerScreen();
1390 if (answerScreen != null) {
1391 transaction.remove(answerScreen.getAnswerScreenFragment());
1392 }
1393
1394 didShowAnswerScreen = false;
1395 return true;
1396 }
1397
1398 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
1399 if (didShowInCallScreen) {
1400 return false;
1401 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001402 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
linyuhc3968e62017-11-20 17:40:50 -08001403 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001404 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1405 didShowInCallScreen = true;
1406 return true;
1407 }
1408
1409 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
1410 if (!didShowInCallScreen) {
1411 return false;
1412 }
1413 InCallScreen inCallScreen = getInCallScreen();
1414 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001415 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -08001416 }
1417 didShowInCallScreen = false;
1418 return true;
1419 }
1420
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001421 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001422 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001423 VideoCallScreen videoCallScreen = getVideoCallScreen();
1424 if (videoCallScreen.getCallId().equals(call.getId())) {
1425 return false;
1426 }
1427 LogUtil.i(
1428 "InCallActivity.showVideoCallScreenFragment",
1429 "video call fragment exists but arguments do not match");
1430 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001431 }
1432
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001433 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
1434
Eric Erfanian90508232017-03-24 09:31:16 -07001435 VideoCallScreen videoCallScreen =
1436 VideoBindings.createVideoCallScreen(
1437 call.getId(), call.getVideoTech().shouldUseSurfaceView());
linyuhc3968e62017-11-20 17:40:50 -08001438 transaction.add(
1439 R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001440
1441 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1442 didShowVideoCallScreen = true;
1443 return true;
1444 }
1445
1446 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
1447 if (!didShowVideoCallScreen) {
1448 return false;
1449 }
1450 VideoCallScreen videoCallScreen = getVideoCallScreen();
1451 if (videoCallScreen != null) {
1452 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
1453 }
1454 didShowVideoCallScreen = false;
1455 return true;
1456 }
1457
linyuhc3968e62017-11-20 17:40:50 -08001458 private AnswerScreen getAnswerScreen() {
1459 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001460 }
1461
linyuhc3968e62017-11-20 17:40:50 -08001462 private InCallScreen getInCallScreen() {
1463 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001464 }
1465
linyuhc3968e62017-11-20 17:40:50 -08001466 private VideoCallScreen getVideoCallScreen() {
1467 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001468 }
1469
1470 @Override
1471 public void onPseudoScreenStateChanged(boolean isOn) {
1472 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
1473 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
1474 }
1475
1476 /**
1477 * For some touch related issue, turning off the screen can be faked by drawing a black view over
1478 * the activity. All touch events started when the screen is "off" is rejected.
1479 *
1480 * @see PseudoScreenState
1481 */
1482 @Override
1483 public boolean dispatchTouchEvent(MotionEvent event) {
1484 // Reject any gesture that started when the screen is in the fake off state.
1485 if (touchDownWhenPseudoScreenOff) {
1486 if (event.getAction() == MotionEvent.ACTION_UP) {
1487 touchDownWhenPseudoScreenOff = false;
1488 }
1489 return true;
1490 }
1491 // Reject all touch event when the screen is in the fake off state.
1492 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
1493 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1494 touchDownWhenPseudoScreenOff = true;
1495 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
1496 }
1497 return true;
1498 }
1499 return super.dispatchTouchEvent(event);
1500 }
1501
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001502 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -08001503 public final boolean shouldShow;
1504 public final DialerCall call;
1505
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001506 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001507 this.shouldShow = shouldShow;
1508 this.call = call;
1509 }
1510 }
linyuhc3968e62017-11-20 17:40:50 -08001511
1512 private static final class IntentExtraNames {
1513 static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent";
1514 static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
1515 static final String SHOW_DIALPAD = "InCallActivity.show_dialpad";
1516 }
1517
1518 private static final class KeysForSavedInstance {
1519 static final String DIALPAD_TEXT = "InCallActivity.dialpad_text";
1520 static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
1521 static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
1522 static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
1523 }
1524
1525 /** Request codes for pending intents. */
1526 public static final class PendingIntentRequestCodes {
1527 static final int NON_FULL_SCREEN = 0;
1528 static final int FULL_SCREEN = 1;
1529 static final int BUBBLE = 2;
1530 }
1531
1532 private static final class Tags {
1533 static final String ANSWER_SCREEN = "tag_answer_screen";
1534 static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment";
1535 static final String IN_CALL_SCREEN = "tag_in_call_screen";
1536 static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
1537 static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
1538 static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
1539 static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
1540 }
1541
1542 private static final class ConfigNames {
1543 static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
1544 }
1545
1546 private static final class InternationalCallOnWifiCallback
1547 implements InternationalCallOnWifiDialogFragment.Callback {
1548 private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName();
1549
1550 @Override
1551 public void continueCall(@NonNull String callId) {
1552 LogUtil.i(TAG, "Continuing call with ID: %s", callId);
1553 }
1554
1555 @Override
1556 public void cancelCall(@NonNull String callId) {
1557 DialerCall call = CallList.getInstance().getCallById(callId);
1558 if (call == null) {
1559 LogUtil.i(TAG, "Call destroyed before the dialog is closed");
1560 return;
1561 }
1562
1563 LogUtil.i(TAG, "Disconnecting international call on WiFi");
1564 call.disconnect();
1565 }
1566 }
1567
1568 private static final class SelectPhoneAccountListener
1569 extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener {
1570 private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName();
1571
1572 @Override
1573 public void onPhoneAccountSelected(
1574 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
1575 DialerCall call = CallList.getInstance().getCallById(callId);
1576 LogUtil.i(TAG, "Phone account select with call:\n%s", call);
1577
1578 if (call != null) {
1579 call.phoneAccountSelected(selectedAccountHandle, setDefault);
1580 }
1581 }
1582
1583 @Override
1584 public void onDialogDismissed(String callId) {
1585 DialerCall call = CallList.getInstance().getCallById(callId);
1586 LogUtil.i(TAG, "Disconnecting call:\n%s" + call);
1587
1588 if (call != null) {
1589 call.disconnect();
1590 }
1591 }
1592 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001593}