blob: 28ff7da60a12160adddd7424eb12f246cc796ad0 [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;
38import android.support.v4.app.Fragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080039import android.support.v4.app.FragmentManager;
40import android.support.v4.app.FragmentTransaction;
linyuh57b093b2017-11-17 14:32:32 -080041import android.support.v4.content.res.ResourcesCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080042import android.support.v4.graphics.ColorUtils;
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;
weijiaxu650e7cc2017-10-31 12:38:54 -070060import com.android.dialer.common.concurrent.ThreadUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080061import com.android.dialer.compat.ActivityCompat;
linyuhc3968e62017-11-20 17:40:50 -080062import com.android.dialer.compat.CompatUtils;
Eric Erfanian2ca43182017-08-31 06:57:16 -070063import com.android.dialer.configprovider.ConfigProviderBindings;
64import com.android.dialer.logging.DialerImpression;
Eric Erfanianccca3152017-02-22 16:32:36 -080065import com.android.dialer.logging.Logger;
weijiaxu94df7202017-10-25 18:21:41 -070066import com.android.dialer.logging.LoggingBindings;
Eric Erfanian8369df02017-05-03 10:27:13 -070067import com.android.dialer.logging.ScreenEvent;
linyuhc3968e62017-11-20 17:40:50 -080068import com.android.dialer.util.ViewUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080069import com.android.incallui.answer.bindings.AnswerBindings;
70import com.android.incallui.answer.protocol.AnswerScreen;
71import com.android.incallui.answer.protocol.AnswerScreenDelegate;
72import com.android.incallui.answer.protocol.AnswerScreenDelegateFactory;
73import com.android.incallui.answerproximitysensor.PseudoScreenState;
linyuh57b093b2017-11-17 14:32:32 -080074import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080075import com.android.incallui.call.CallList;
76import com.android.incallui.call.DialerCall;
77import com.android.incallui.call.DialerCall.State;
linyuh57b093b2017-11-17 14:32:32 -080078import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070079import com.android.incallui.callpending.CallPendingActivity;
80import com.android.incallui.disconnectdialog.DisconnectMessage;
Eric Erfanianccca3152017-02-22 16:32:36 -080081import com.android.incallui.incall.bindings.InCallBindings;
82import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
83import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
84import com.android.incallui.incall.protocol.InCallScreen;
85import com.android.incallui.incall.protocol.InCallScreenDelegate;
86import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
linyuh7b86f562017-11-16 11:24:09 -080087import com.android.incallui.incalluilock.InCallUiLock;
linyuhf99f6302017-11-15 11:23:51 -080088import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080089import com.android.incallui.video.bindings.VideoBindings;
90import com.android.incallui.video.protocol.VideoCallScreen;
91import com.android.incallui.video.protocol.VideoCallScreenDelegate;
92import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
linyuhc3968e62017-11-20 17:40:50 -080093import com.google.common.base.Optional;
94import java.lang.annotation.Retention;
95import java.lang.annotation.RetentionPolicy;
96import java.util.ArrayList;
97import java.util.List;
Eric Erfanianccca3152017-02-22 16:32:36 -080098
99/** Version of {@link InCallActivity} that shows the new UI */
100public class InCallActivity extends TransactionSafeFragmentActivity
101 implements AnswerScreenDelegateFactory,
102 InCallScreenDelegateFactory,
103 InCallButtonUiDelegateFactory,
104 VideoCallScreenDelegateFactory,
105 PseudoScreenState.StateChangedListener {
106
linyuhc3968e62017-11-20 17:40:50 -0800107 @Retention(RetentionPolicy.SOURCE)
108 @IntDef({
109 DIALPAD_REQUEST_NONE,
110 DIALPAD_REQUEST_SHOW,
111 DIALPAD_REQUEST_HIDE,
112 })
113 @interface DialpadRequestType {}
Eric Erfanian2ca43182017-08-31 06:57:16 -0700114
linyuhc3968e62017-11-20 17:40:50 -0800115 private static final int DIALPAD_REQUEST_NONE = 1;
116 private static final int DIALPAD_REQUEST_SHOW = 2;
117 private static final int DIALPAD_REQUEST_HIDE = 3;
linyuh57b093b2017-11-17 14:32:32 -0800118
linyuhc3968e62017-11-20 17:40:50 -0800119 private static Optional<Integer> audioRouteForTesting = Optional.absent();
linyuh57b093b2017-11-17 14:32:32 -0800120
linyuhc3968e62017-11-20 17:40:50 -0800121 private final InternationalCallOnWifiCallback internationalCallOnWifiCallback =
122 new InternationalCallOnWifiCallback();
123 private final SelectPhoneAccountListener selectPhoneAccountListener =
124 new SelectPhoneAccountListener();
Eric Erfanianccca3152017-02-22 16:32:36 -0800125
linyuhc3968e62017-11-20 17:40:50 -0800126 private Animation dialpadSlideInAnimation;
127 private Animation dialpadSlideOutAnimation;
128 private Dialog errorDialog;
129 private GradientDrawable backgroundDrawable;
linyuh69a25062017-11-15 16:18:51 -0800130 private InCallOrientationEventListener inCallOrientationEventListener;
linyuhc3968e62017-11-20 17:40:50 -0800131 private View pseudoBlackScreenOverlay;
132 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
133 private String dtmfTextToPrepopulate;
134 private String showPostCharWaitDialogCallId;
135 private String showPostCharWaitDialogChars;
136 private boolean allowOrientationChange;
137 private boolean animateDialpadOnShow;
Eric Erfanianccca3152017-02-22 16:32:36 -0800138 private boolean didShowAnswerScreen;
139 private boolean didShowInCallScreen;
140 private boolean didShowVideoCallScreen;
linyuh9c327da2017-11-14 12:33:48 -0800141 private boolean dismissKeyguard;
Eric Erfanianccca3152017-02-22 16:32:36 -0800142 private boolean isInShowMainInCallFragment;
linyuhc3968e62017-11-20 17:40:50 -0800143 private boolean isRecreating; // whether the activity is going to be recreated
144 private boolean isVisible;
Eric Erfanianccca3152017-02-22 16:32:36 -0800145 private boolean needDismissPendingDialogs;
linyuhc3968e62017-11-20 17:40:50 -0800146 private boolean showPostCharWaitDialogOnResume;
147 private boolean touchDownWhenPseudoScreenOff;
148 private int[] backgroundDrawableColors;
149 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800150
151 public static Intent getIntent(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700152 Context context, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800153 Intent intent = new Intent(Intent.ACTION_MAIN, null);
154 intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
155 intent.setClass(context, InCallActivity.class);
linyuhc3968e62017-11-20 17:40:50 -0800156 if (showDialpad) {
157 intent.putExtra(IntentExtraNames.SHOW_DIALPAD, true);
158 }
159 intent.putExtra(IntentExtraNames.NEW_OUTGOING_CALL, newOutgoingCall);
160 intent.putExtra(IntentExtraNames.FOR_FULL_SCREEN, isForFullScreen);
Eric Erfanianccca3152017-02-22 16:32:36 -0800161 return intent;
162 }
163
164 @Override
165 protected void onResumeFragments() {
166 super.onResumeFragments();
167 if (needDismissPendingDialogs) {
168 dismissPendingDialogs();
169 }
170 }
171
172 @Override
linyuhc3968e62017-11-20 17:40:50 -0800173 protected void onCreate(Bundle bundle) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700174 Trace.beginSection("InCallActivity.onCreate");
linyuhc3968e62017-11-20 17:40:50 -0800175 super.onCreate(bundle);
Eric Erfanianccca3152017-02-22 16:32:36 -0800176
Eric Erfanian2ca43182017-08-31 06:57:16 -0700177 if (getIntent().getBooleanExtra(ReturnToCallController.RETURN_TO_CALL_EXTRA_KEY, false)) {
178 Logger.get(this).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL);
179 getIntent().removeExtra(ReturnToCallController.RETURN_TO_CALL_EXTRA_KEY);
180 }
181
linyuhc3968e62017-11-20 17:40:50 -0800182 if (bundle != null) {
183 didShowAnswerScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN);
184 didShowInCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN);
185 didShowVideoCallScreen = bundle.getBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -0800186 }
187
linyuhc3968e62017-11-20 17:40:50 -0800188 setWindowFlags();
189 setContentView(R.layout.incall_screen);
190 internalResolveIntent(getIntent());
191
192 boolean isLandscape =
193 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
194 boolean isRtl = ViewUtil.isRtl();
195 if (isLandscape) {
196 dialpadSlideInAnimation =
197 AnimationUtils.loadAnimation(
198 this, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
199 dialpadSlideOutAnimation =
200 AnimationUtils.loadAnimation(
201 this, isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
202 } else {
203 dialpadSlideInAnimation = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
204 dialpadSlideOutAnimation =
205 AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
206 }
207 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
208 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
209 dialpadSlideOutAnimation.setAnimationListener(
210 new AnimationListenerAdapter() {
211 @Override
212 public void onAnimationEnd(Animation animation) {
213 hideDialpadFragment();
214 }
215 });
216
217 if (bundle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
218 // If the dialpad was shown before, set related variables so that it can be shown and
219 // populated with the previous DTMF text during onResume().
220 if (bundle.containsKey(IntentExtraNames.SHOW_DIALPAD)) {
221 boolean showDialpad = bundle.getBoolean(IntentExtraNames.SHOW_DIALPAD);
222 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
223 animateDialpadOnShow = false;
224 }
225 dtmfTextToPrepopulate = bundle.getString(KeysForSavedInstance.DIALPAD_TEXT);
226
227 SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
228 (SelectPhoneAccountDialogFragment)
229 getFragmentManager().findFragmentByTag(Tags.SELECT_ACCOUNT_FRAGMENT);
230 if (selectPhoneAccountDialogFragment != null) {
231 selectPhoneAccountDialogFragment.setListener(selectPhoneAccountListener);
232 }
233 }
234
235 InternationalCallOnWifiDialogFragment existingInternationalCallOnWifiDialogFragment =
236 (InternationalCallOnWifiDialogFragment)
237 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
238 if (existingInternationalCallOnWifiDialogFragment != null) {
239 existingInternationalCallOnWifiDialogFragment.setCallback(internationalCallOnWifiCallback);
240 }
241
linyuh69a25062017-11-15 16:18:51 -0800242 inCallOrientationEventListener = new InCallOrientationEventListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800243
244 getWindow()
245 .getDecorView()
246 .setSystemUiVisibility(
247 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
248
249 pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700250 sendBroadcast(CallPendingActivity.getFinishBroadcast());
251 Trace.endSection();
weijiaxuc950a9b2017-11-06 16:39:04 -0800252 Logger.get(this)
253 .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
254 Logger.get(this)
255 .logStopLatencyTimer(LoggingBindings.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800256 }
257
linyuhc3968e62017-11-20 17:40:50 -0800258 private void setWindowFlags() {
259 // Allow the activity to be shown when the screen is locked and filter out touch events that are
260 // "too fat".
261 int flags =
262 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
263 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
264
265 // When the audio stream is not directed through Bluetooth, turn the screen on once the
266 // activity is shown.
267 final int audioRoute = getAudioRoute();
268 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH) {
269 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
270 }
271
272 getWindow().addFlags(flags);
273 }
274
275 private static int getAudioRoute() {
276 if (audioRouteForTesting.isPresent()) {
277 return audioRouteForTesting.get();
278 }
279
280 return AudioModeProvider.getInstance().getAudioState().getRoute();
281 }
282
283 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
284 public static void setAudioRouteForTesting(int audioRoute) {
285 audioRouteForTesting = Optional.of(audioRoute);
286 }
287
288 private void internalResolveIntent(Intent intent) {
289 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
290 return;
291 }
292
293 if (intent.hasExtra(IntentExtraNames.SHOW_DIALPAD)) {
294 // IntentExtraNames.SHOW_DIALPAD can be used to specify whether the DTMF dialpad should be
295 // initially visible. If the extra is absent, leave the dialpad in its previous state.
296 boolean showDialpad = intent.getBooleanExtra(IntentExtraNames.SHOW_DIALPAD, false);
297 relaunchedFromDialer(showDialpad);
298 }
299
300 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
301 if (outgoingCall == null) {
302 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
303 }
304 if (intent.getBooleanExtra(IntentExtraNames.NEW_OUTGOING_CALL, false)) {
305 intent.removeExtra(IntentExtraNames.NEW_OUTGOING_CALL);
306
307 // InCallActivity is responsible for disconnecting a new outgoing call if there is no way of
308 // making it (i.e. no valid call capable accounts).
309 // If the version is not MSIM compatible, ignore this code.
310 if (CompatUtils.isMSIMCompatible()
311 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
312 LogUtil.i(
313 "InCallActivity.internalResolveIntent", "Call with no valid accounts, disconnecting");
314 outgoingCall.disconnect();
315 }
316
317 dismissKeyguard(true);
318 }
319
320 if (showPhoneAccountSelectionDialog()) {
321 hideMainInCallFragment();
322 }
323 }
324
325 /**
326 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
327 * be shown on launch.
328 *
329 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
330 * false} to indicate no change should be made to the dialpad visibility.
331 */
332 private void relaunchedFromDialer(boolean showDialpad) {
333 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
334 animateDialpadOnShow = true;
335
336 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
337 // If there's only one line in use, AND it's on hold, then we're sure the user
338 // wants to use the dialpad toward the exact line, so un-hold the holding line.
339 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
340 if (call != null && call.getState() == State.ONHOLD) {
341 call.unhold();
342 }
343 }
344 }
345
346 /**
347 * Show a phone account selection dialog if there is a call waiting for phone account selection.
348 *
349 * @return true if the dialog was shown.
350 */
351 private boolean showPhoneAccountSelectionDialog() {
352 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
353 if (waitingForAccountCall == null) {
354 return false;
355 }
356
357 Bundle extras = waitingForAccountCall.getIntentExtras();
358 List<PhoneAccountHandle> phoneAccountHandles =
359 extras == null
360 ? new ArrayList<>()
361 : extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
362
363 selectPhoneAccountDialogFragment =
364 SelectPhoneAccountDialogFragment.newInstance(
365 R.string.select_phone_account_for_calls,
366 true /* canSetDefault */,
367 0 /* setDefaultResId */,
368 phoneAccountHandles,
369 selectPhoneAccountListener,
370 waitingForAccountCall.getId(),
371 null /* hints */);
372 selectPhoneAccountDialogFragment.show(getFragmentManager(), Tags.SELECT_ACCOUNT_FRAGMENT);
373 return true;
374 }
375
Eric Erfanianccca3152017-02-22 16:32:36 -0800376 @Override
377 protected void onSaveInstanceState(Bundle out) {
linyuh57b093b2017-11-17 14:32:32 -0800378 LogUtil.enterBlock("InCallActivity.onSaveInstanceState");
379
380 // TODO: DialpadFragment should handle this as part of its own state
linyuhc3968e62017-11-20 17:40:50 -0800381 out.putBoolean(IntentExtraNames.SHOW_DIALPAD, isDialpadVisible());
linyuh57b093b2017-11-17 14:32:32 -0800382 DialpadFragment dialpadFragment = getDialpadFragment();
383 if (dialpadFragment != null) {
linyuhc3968e62017-11-20 17:40:50 -0800384 out.putString(KeysForSavedInstance.DIALPAD_TEXT, dialpadFragment.getDtmfText());
linyuh57b093b2017-11-17 14:32:32 -0800385 }
386
linyuhc3968e62017-11-20 17:40:50 -0800387 out.putBoolean(KeysForSavedInstance.DID_SHOW_ANSWER_SCREEN, didShowAnswerScreen);
388 out.putBoolean(KeysForSavedInstance.DID_SHOW_IN_CALL_SCREEN, didShowInCallScreen);
389 out.putBoolean(KeysForSavedInstance.DID_SHOW_VIDEO_CALL_SCREEN, didShowVideoCallScreen);
linyuh57b093b2017-11-17 14:32:32 -0800390
Eric Erfanianccca3152017-02-22 16:32:36 -0800391 super.onSaveInstanceState(out);
392 isVisible = false;
393 }
394
395 @Override
396 protected void onStart() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700397 Trace.beginSection("InCallActivity.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800398 super.onStart();
linyuh57b093b2017-11-17 14:32:32 -0800399
Eric Erfanianccca3152017-02-22 16:32:36 -0800400 isVisible = true;
401 showMainInCallFragment();
linyuh57b093b2017-11-17 14:32:32 -0800402
403 InCallPresenter.getInstance().setActivity(this);
404 enableInCallOrientationEventListener(
405 getRequestedOrientation()
406 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
407 InCallPresenter.getInstance().onActivityStarted();
408
Eric Erfanianccca3152017-02-22 16:32:36 -0800409 if (ActivityCompat.isInMultiWindowMode(this)
410 && !getResources().getBoolean(R.bool.incall_dialpad_allowed)) {
411 // Hide the dialpad because there may not be enough room
412 showDialpadFragment(false, false);
413 }
linyuh57b093b2017-11-17 14:32:32 -0800414
Eric Erfanian2ca43182017-08-31 06:57:16 -0700415 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800416 }
417
418 @Override
419 protected void onResume() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700420 Trace.beginSection("InCallActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800421 super.onResume();
linyuhc3968e62017-11-20 17:40:50 -0800422
423 if (!InCallPresenter.getInstance().isReadyForTearDown()) {
424 updateTaskDescription();
425 InCallPresenter.getInstance().onUiShowing(true);
426 }
427
428 // If there is a pending request to show or hide the dialpad, handle that now.
429 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
430 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
431 // Exit fullscreen so that the user has access to the dialpad hide/show button.
432 // This is important when showing the dialpad from within dialer.
433 InCallPresenter.getInstance().setFullScreen(false /* isFullScreen */, true /* force */);
434
435 showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
436 animateDialpadOnShow = false;
437
438 DialpadFragment dialpadFragment = getDialpadFragment();
439 if (dialpadFragment != null) {
440 dialpadFragment.setDtmfText(dtmfTextToPrepopulate);
441 dtmfTextToPrepopulate = null;
442 }
443 } else {
444 LogUtil.i("InCallActivity.onResume", "Force-hide the dialpad");
445 if (getDialpadFragment() != null) {
446 showDialpadFragment(false /* show */, false /* animate */);
447 }
448 }
449 showDialpadRequest = DIALPAD_REQUEST_NONE;
450 }
451 updateNavigationBar(isDialpadVisible());
452
453 if (showPostCharWaitDialogOnResume) {
454 showDialogForPostCharWait(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
455 }
456
457 CallList.getInstance()
458 .onInCallUiShown(getIntent().getBooleanExtra(IntentExtraNames.FOR_FULL_SCREEN, false));
459
Eric Erfanianccca3152017-02-22 16:32:36 -0800460 PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
461 pseudoScreenState.addListener(this);
462 onPseudoScreenStateChanged(pseudoScreenState.isOn());
Eric Erfanian2ca43182017-08-31 06:57:16 -0700463 Trace.endSection();
weijiaxu650e7cc2017-10-31 12:38:54 -0700464 // add 1 sec delay to get memory snapshot so that dialer wont react slowly on resume.
465 ThreadUtil.postDelayedOnUiThread(
weijiaxuc950a9b2017-11-06 16:39:04 -0800466 () ->
467 Logger.get(this)
468 .logRecordMemory(LoggingBindings.INCALL_ACTIVITY_ON_RESUME_MEMORY_EVENT_NAME),
weijiaxu650e7cc2017-10-31 12:38:54 -0700469 1000);
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 }
471
Eric Erfanianccca3152017-02-22 16:32:36 -0800472 @Override
473 protected void onPause() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700474 Trace.beginSection("InCallActivity.onPause");
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 super.onPause();
linyuh57b093b2017-11-17 14:32:32 -0800476
477 DialpadFragment dialpadFragment = getDialpadFragment();
478 if (dialpadFragment != null) {
479 dialpadFragment.onDialerKeyUp(null);
480 }
481
482 InCallPresenter.getInstance().onUiShowing(false);
483 if (isFinishing()) {
484 InCallPresenter.getInstance().unsetActivity(this);
485 }
486
Eric Erfanianccca3152017-02-22 16:32:36 -0800487 InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700488 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800489 }
490
491 @Override
492 protected void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700493 Trace.beginSection("InCallActivity.onStop");
wangqi4d705e52017-09-28 12:23:35 -0700494 isVisible = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800495 super.onStop();
linyuh57b093b2017-11-17 14:32:32 -0800496
497 // Disconnects the call waiting for a phone account when the activity is hidden (e.g., after the
498 // user presses the home button).
499 // Without this the pending call will get stuck on phone account selection and new calls can't
500 // be created.
501 // Skip this when the screen is locked since the activity may complete its current life cycle
502 // and restart.
linyuhc3968e62017-11-20 17:40:50 -0800503 if (!isRecreating && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
linyuh57b093b2017-11-17 14:32:32 -0800504 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
505 if (waitingForAccountCall != null) {
506 waitingForAccountCall.disconnect();
507 }
508 }
509
510 enableInCallOrientationEventListener(false);
511 InCallPresenter.getInstance().updateIsChangingConfigurations();
512 InCallPresenter.getInstance().onActivityStopped();
linyuhc3968e62017-11-20 17:40:50 -0800513 if (!isRecreating) {
linyuh57b093b2017-11-17 14:32:32 -0800514 if (errorDialog != null) {
515 errorDialog.dismiss();
516 }
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
593 private void onNewIntent(Intent intent, boolean isRecreating) {
594 this.isRecreating = isRecreating;
595
596 // We're being re-launched with a new Intent. Since it's possible for a single InCallActivity
597 // instance to persist indefinitely (even if we finish() ourselves), this sequence can
598 // happen any time the InCallActivity needs to be displayed.
599
600 // Stash away the new intent so that we can get it in the future by calling getIntent().
601 // Otherwise getIntent() will return the original Intent from when we first got created.
602 setIntent(intent);
603
604 // Activities are always paused before receiving a new intent, so we can count on our onResume()
605 // method being called next.
606
607 // Just like in onCreate(), handle the intent.
608 // Skip if InCallActivity is going to be recreated since this will be called in onCreate().
609 if (!isRecreating) {
610 internalResolveIntent(intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800611 }
612 }
613
614 @Override
615 public void onBackPressed() {
linyuh57b093b2017-11-17 14:32:32 -0800616 LogUtil.enterBlock("InCallActivity.onBackPressed");
617
linyuhc3968e62017-11-20 17:40:50 -0800618 if (!isVisible) {
linyuh57b093b2017-11-17 14:32:32 -0800619 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800620 }
linyuh57b093b2017-11-17 14:32:32 -0800621
622 if (!getCallCardFragmentVisible()) {
623 return;
624 }
625
626 DialpadFragment dialpadFragment = getDialpadFragment();
627 if (dialpadFragment != null && dialpadFragment.isVisible()) {
628 showDialpadFragment(false /* show */, true /* animate */);
629 return;
630 }
631
632 if (CallList.getInstance().getIncomingCall() != null) {
633 LogUtil.i(
634 "InCallActivity.onBackPressed",
635 "Ignore the press of the back key when an incoming call is ringing");
636 return;
637 }
638
639 // Nothing special to do. Fall back to the default behavior.
640 super.onBackPressed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800641 }
642
643 @Override
644 public boolean onOptionsItemSelected(MenuItem item) {
645 LogUtil.i("InCallActivity.onOptionsItemSelected", "item: " + item);
646 if (item.getItemId() == android.R.id.home) {
647 onBackPressed();
648 return true;
649 }
650 return super.onOptionsItemSelected(item);
651 }
652
653 @Override
654 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800655 DialpadFragment dialpadFragment = getDialpadFragment();
656 if (dialpadFragment != null
657 && dialpadFragment.isVisible()
658 && dialpadFragment.onDialerKeyUp(event)) {
659 return true;
660 }
661
662 if (keyCode == KeyEvent.KEYCODE_CALL) {
663 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
664 return true;
665 }
666
667 return super.onKeyUp(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800668 }
669
670 @Override
671 public boolean onKeyDown(int keyCode, KeyEvent event) {
linyuh57b093b2017-11-17 14:32:32 -0800672 switch (keyCode) {
673 case KeyEvent.KEYCODE_CALL:
674 if (!InCallPresenter.getInstance().handleCallKey()) {
675 LogUtil.e(
676 "InCallActivity.onKeyDown",
677 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
678 }
679 // Always consume KEYCODE_CALL to ensure the PhoneWindow won't do anything with it.
680 return true;
681
682 // Note that KEYCODE_ENDCALL isn't handled here as the standard system-wide handling of it
683 // is exactly what's needed, namely
684 // (1) "hang up" if there's an active call, or
685 // (2) "don't answer" if there's an incoming call.
686 // (See PhoneWindowManager for implementation details.)
687
688 case KeyEvent.KEYCODE_CAMERA:
689 // Consume KEYCODE_CAMERA since it's easy to accidentally press the camera button.
690 return true;
691
692 case KeyEvent.KEYCODE_VOLUME_UP:
693 case KeyEvent.KEYCODE_VOLUME_DOWN:
694 case KeyEvent.KEYCODE_VOLUME_MUTE:
695 // Ringer silencing handled by PhoneWindowManager.
696 break;
697
698 case KeyEvent.KEYCODE_MUTE:
699 TelecomAdapter.getInstance()
700 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
701 return true;
702
703 case KeyEvent.KEYCODE_SLASH:
704 // When verbose logging is enabled, dump the view for debugging/testing purposes.
705 if (LogUtil.isVerboseEnabled()) {
706 View decorView = getWindow().getDecorView();
707 LogUtil.v("InCallActivity.onKeyDown", "View dump:\n%s", decorView);
708 return true;
709 }
710 break;
711
712 case KeyEvent.KEYCODE_EQUALS:
713 break;
714
715 default: // fall out
716 }
717
718 // Pass other key events to DialpadFragment's "onDialerKeyDown" method in case the user types
719 // in DTMF (Dual-tone multi-frequency signaling) code.
720 DialpadFragment dialpadFragment = getDialpadFragment();
721 if (dialpadFragment != null
722 && dialpadFragment.isVisible()
723 && dialpadFragment.onDialerKeyDown(event)) {
724 return true;
725 }
726
727 return super.onKeyDown(keyCode, event);
Eric Erfanianccca3152017-02-22 16:32:36 -0800728 }
729
730 public boolean isInCallScreenAnimating() {
731 return false;
732 }
733
734 public void showConferenceFragment(boolean show) {
735 if (show) {
736 startActivity(new Intent(this, ManageConferenceActivity.class));
737 }
738 }
739
linyuhc3968e62017-11-20 17:40:50 -0800740 public void showDialpadFragment(boolean show, boolean animate) {
741 if (show == isDialpadVisible()) {
742 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800743 }
linyuhc3968e62017-11-20 17:40:50 -0800744
745 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
746 if (dialpadFragmentManager == null) {
747 LogUtil.i("InCallActivity.showDialpadFragment", "Unable to obtain a FragmentManager");
748 return;
749 }
750
751 if (!animate) {
752 if (show) {
753 showDialpadFragment();
754 } else {
755 hideDialpadFragment();
756 }
757 } else {
758 if (show) {
759 showDialpadFragment();
760 getDialpadFragment().animateShowDialpad();
761 }
762 getDialpadFragment()
763 .getView()
764 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
765 }
766
767 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
768 if (sensor != null) {
769 sensor.onDialpadVisible(show);
770 }
771 showDialpadRequest = DIALPAD_REQUEST_NONE;
772
773 // Note: onInCallScreenDialpadVisibilityChange is called here to ensure that the dialpad FAB
774 // repositions itself.
775 getInCallScreen().onInCallScreenDialpadVisibilityChange(show);
776 }
777
778 private void showDialpadFragment() {
779 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
780 if (dialpadFragmentManager == null) {
781 return;
782 }
783
784 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
785 DialpadFragment dialpadFragment = getDialpadFragment();
786 if (dialpadFragment == null) {
787 transaction.add(getDialpadContainerId(), new DialpadFragment(), Tags.DIALPAD_FRAGMENT);
788 } else {
789 transaction.show(dialpadFragment);
790 }
791 transaction.commitAllowingStateLoss();
792 dialpadFragmentManager.executePendingTransactions();
793
794 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, this);
795 updateNavigationBar(true /* isDialpadVisible */);
796 }
797
798 private void hideDialpadFragment() {
799 FragmentManager dialpadFragmentManager = getDialpadFragmentManager();
800 if (dialpadFragmentManager == null) {
801 return;
802 }
803
804 Fragment dialpadFragment = dialpadFragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
805 if (dialpadFragment != null) {
806 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
807 transaction.hide(dialpadFragment);
808 transaction.commitAllowingStateLoss();
809 dialpadFragmentManager.executePendingTransactions();
810 }
811 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800812 }
813
814 public boolean isDialpadVisible() {
linyuh69a25062017-11-15 16:18:51 -0800815 DialpadFragment dialpadFragment = getDialpadFragment();
816 return dialpadFragment != null && dialpadFragment.isVisible();
817 }
818
linyuhc3968e62017-11-20 17:40:50 -0800819 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
linyuh69a25062017-11-15 16:18:51 -0800820 @Nullable
linyuhc3968e62017-11-20 17:40:50 -0800821 private DialpadFragment getDialpadFragment() {
linyuh69a25062017-11-15 16:18:51 -0800822 FragmentManager fragmentManager = getDialpadFragmentManager();
823 if (fragmentManager == null) {
824 return null;
825 }
linyuhc3968e62017-11-20 17:40:50 -0800826 return (DialpadFragment) fragmentManager.findFragmentByTag(Tags.DIALPAD_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 }
828
829 public void onForegroundCallChanged(DialerCall newForegroundCall) {
linyuh57b093b2017-11-17 14:32:32 -0800830 updateTaskDescription();
831
832 if (newForegroundCall == null || !didShowAnswerScreen) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800833 LogUtil.v("InCallActivity.onForegroundCallChanged", "resetting background color");
linyuh57b093b2017-11-17 14:32:32 -0800834 updateWindowBackgroundColor(0 /* progress */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800835 }
836 }
837
linyuhc3968e62017-11-20 17:40:50 -0800838 private void updateTaskDescription() {
linyuh57b093b2017-11-17 14:32:32 -0800839 int color =
840 getResources().getBoolean(R.bool.is_layout_landscape)
841 ? ResourcesCompat.getColor(
842 getResources(), R.color.statusbar_background_color, getTheme())
843 : InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
844 setTaskDescription(
845 new TaskDescription(
846 getResources().getString(R.string.notification_ongoing_call), null /* icon */, color));
847 }
848
Eric Erfanianccca3152017-02-22 16:32:36 -0800849 public void updateWindowBackgroundColor(@FloatRange(from = -1f, to = 1.0f) float progress) {
850 ThemeColorManager themeColorManager = InCallPresenter.getInstance().getThemeColorManager();
851 @ColorInt int top;
852 @ColorInt int middle;
853 @ColorInt int bottom;
854 @ColorInt int gray = 0x66000000;
855
856 if (ActivityCompat.isInMultiWindowMode(this)) {
857 top = themeColorManager.getBackgroundColorSolid();
858 middle = themeColorManager.getBackgroundColorSolid();
859 bottom = themeColorManager.getBackgroundColorSolid();
860 } else {
861 top = themeColorManager.getBackgroundColorTop();
862 middle = themeColorManager.getBackgroundColorMiddle();
863 bottom = themeColorManager.getBackgroundColorBottom();
864 }
865
866 if (progress < 0) {
867 float correctedProgress = Math.abs(progress);
868 top = ColorUtils.blendARGB(top, gray, correctedProgress);
869 middle = ColorUtils.blendARGB(middle, gray, correctedProgress);
870 bottom = ColorUtils.blendARGB(bottom, gray, correctedProgress);
871 }
872
873 boolean backgroundDirty = false;
874 if (backgroundDrawable == null) {
875 backgroundDrawableColors = new int[] {top, middle, bottom};
876 backgroundDrawable = new GradientDrawable(Orientation.TOP_BOTTOM, backgroundDrawableColors);
877 backgroundDirty = true;
878 } else {
879 if (backgroundDrawableColors[0] != top) {
880 backgroundDrawableColors[0] = top;
881 backgroundDirty = true;
882 }
883 if (backgroundDrawableColors[1] != middle) {
884 backgroundDrawableColors[1] = middle;
885 backgroundDirty = true;
886 }
887 if (backgroundDrawableColors[2] != bottom) {
888 backgroundDrawableColors[2] = bottom;
889 backgroundDirty = true;
890 }
891 if (backgroundDirty) {
892 backgroundDrawable.setColors(backgroundDrawableColors);
893 }
894 }
895
896 if (backgroundDirty) {
897 getWindow().setBackgroundDrawable(backgroundDrawable);
898 }
899 }
900
901 public boolean isVisible() {
902 return isVisible;
903 }
904
905 public boolean getCallCardFragmentVisible() {
906 return didShowInCallScreen || didShowVideoCallScreen;
907 }
908
909 public void dismissKeyguard(boolean dismiss) {
linyuh9c327da2017-11-14 12:33:48 -0800910 if (dismissKeyguard == dismiss) {
911 return;
912 }
913
914 dismissKeyguard = dismiss;
915 if (dismiss) {
916 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
917 } else {
918 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
919 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800920 }
921
linyuhc3968e62017-11-20 17:40:50 -0800922 public void showDialogForPostCharWait(String callId, String chars) {
923 if (isVisible) {
924 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
925 fragment.show(getSupportFragmentManager(), Tags.POST_CHAR_DIALOG_FRAGMENT);
926
927 showPostCharWaitDialogOnResume = false;
928 showPostCharWaitDialogCallId = null;
929 showPostCharWaitDialogChars = null;
930 } else {
931 showPostCharWaitDialogOnResume = true;
932 showPostCharWaitDialogCallId = callId;
933 showPostCharWaitDialogChars = chars;
934 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800935 }
936
linyuh7b86f562017-11-16 11:24:09 -0800937 public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
938 LogUtil.i(
939 "InCallActivity.showDialogOrToastForDisconnectedCall",
940 "disconnect cause: %s",
941 disconnectMessage);
942
943 if (disconnectMessage.dialog == null || isFinishing()) {
944 return;
945 }
946
947 dismissPendingDialogs();
948
949 // Show a toast if the app is in background when a dialog can't be visible.
950 if (!isVisible()) {
951 Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
952 .show();
953 return;
954 }
955
956 // Show the dialog.
linyuhc3968e62017-11-20 17:40:50 -0800957 errorDialog = disconnectMessage.dialog;
linyuh7b86f562017-11-16 11:24:09 -0800958 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
959 disconnectMessage.dialog.setOnDismissListener(
960 dialogInterface -> {
961 lock.release();
962 onDialogDismissed();
963 });
964 disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
965 disconnectMessage.dialog.show();
966 }
967
968 private void onDialogDismissed() {
linyuhc3968e62017-11-20 17:40:50 -0800969 errorDialog = null;
linyuh7b86f562017-11-16 11:24:09 -0800970 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800971 }
972
973 public void dismissPendingDialogs() {
linyuhc3968e62017-11-20 17:40:50 -0800974 LogUtil.enterBlock("InCallActivity.dismissPendingDialogs");
linyuhf99f6302017-11-15 11:23:51 -0800975
976 if (!isVisible) {
977 // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
978 // been called.
Eric Erfanianccca3152017-02-22 16:32:36 -0800979 LogUtil.i(
980 "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
981 needDismissPendingDialogs = true;
linyuhf99f6302017-11-15 11:23:51 -0800982 return;
Eric Erfanianccca3152017-02-22 16:32:36 -0800983 }
linyuhf99f6302017-11-15 11:23:51 -0800984
985 // Dismiss the error dialog
linyuhf99f6302017-11-15 11:23:51 -0800986 if (errorDialog != null) {
987 errorDialog.dismiss();
linyuhc3968e62017-11-20 17:40:50 -0800988 errorDialog = null;
linyuhf99f6302017-11-15 11:23:51 -0800989 }
990
991 // Dismiss the phone account selection dialog
linyuhf99f6302017-11-15 11:23:51 -0800992 if (selectPhoneAccountDialogFragment != null) {
993 selectPhoneAccountDialogFragment.dismiss();
linyuhc3968e62017-11-20 17:40:50 -0800994 selectPhoneAccountDialogFragment = null;
linyuhf99f6302017-11-15 11:23:51 -0800995 }
996
997 // Dismiss the dialog for international call on WiFi
998 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
999 (InternationalCallOnWifiDialogFragment)
linyuhc3968e62017-11-20 17:40:50 -08001000 getSupportFragmentManager().findFragmentByTag(Tags.INTERNATIONAL_CALL_ON_WIFI);
linyuhf99f6302017-11-15 11:23:51 -08001001 if (internationalCallOnWifiFragment != null) {
1002 internationalCallOnWifiFragment.dismiss();
1003 }
1004
1005 // Dismiss the answer screen
1006 AnswerScreen answerScreen = getAnswerScreen();
1007 if (answerScreen != null) {
1008 answerScreen.dismissPendingDialogs();
1009 }
1010
1011 needDismissPendingDialogs = false;
Eric Erfanianccca3152017-02-22 16:32:36 -08001012 }
1013
linyuhc3968e62017-11-20 17:40:50 -08001014 private void enableInCallOrientationEventListener(boolean enable) {
linyuh69a25062017-11-15 16:18:51 -08001015 if (enable) {
1016 inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
1017 } else {
1018 inCallOrientationEventListener.disable();
1019 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001020 }
1021
1022 public void setExcludeFromRecents(boolean exclude) {
linyuhc3968e62017-11-20 17:40:50 -08001023 int taskId = getTaskId();
1024
1025 List<AppTask> tasks = getSystemService(ActivityManager.class).getAppTasks();
1026 for (AppTask task : tasks) {
1027 try {
1028 if (task.getTaskInfo().id == taskId) {
1029 task.setExcludeFromRecents(exclude);
1030 }
1031 } catch (RuntimeException e) {
1032 LogUtil.e("InCallActivity.setExcludeFromRecents", "RuntimeException:\n%s", e);
1033 }
1034 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001035 }
1036
Eric Erfanianccca3152017-02-22 16:32:36 -08001037 @Nullable
1038 public FragmentManager getDialpadFragmentManager() {
1039 InCallScreen inCallScreen = getInCallScreen();
1040 if (inCallScreen != null) {
1041 return inCallScreen.getInCallScreenFragment().getChildFragmentManager();
1042 }
1043 return null;
1044 }
1045
1046 public int getDialpadContainerId() {
1047 return getInCallScreen().getAnswerAndDialpadContainerResourceId();
1048 }
1049
1050 @Override
1051 public AnswerScreenDelegate newAnswerScreenDelegate(AnswerScreen answerScreen) {
1052 DialerCall call = CallList.getInstance().getCallById(answerScreen.getCallId());
1053 if (call == null) {
1054 // This is a work around for a bug where we attempt to create a new delegate after the call
1055 // has already been removed. An example of when this can happen is:
1056 // 1. incoming video call in landscape mode
1057 // 2. remote party hangs up
1058 // 3. activity switches from landscape to portrait
1059 // At step #3 the answer fragment will try to create a new answer delegate but the call won't
1060 // exist. In this case we'll simply return a stub delegate that does nothing. This is ok
1061 // because this new state is transient and the activity will be destroyed soon.
1062 LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "call doesn't exist, using stub");
1063 return new AnswerScreenPresenterStub();
1064 } else {
1065 return new AnswerScreenPresenter(
1066 this, answerScreen, CallList.getInstance().getCallById(answerScreen.getCallId()));
1067 }
1068 }
1069
1070 @Override
1071 public InCallScreenDelegate newInCallScreenDelegate() {
1072 return new CallCardPresenter(this);
1073 }
1074
1075 @Override
1076 public InCallButtonUiDelegate newInCallButtonUiDelegate() {
1077 return new CallButtonPresenter(this);
1078 }
1079
1080 @Override
Eric Erfanian90508232017-03-24 09:31:16 -07001081 public VideoCallScreenDelegate newVideoCallScreenDelegate(VideoCallScreen videoCallScreen) {
1082 DialerCall dialerCall = CallList.getInstance().getCallById(videoCallScreen.getCallId());
1083 if (dialerCall != null && dialerCall.getVideoTech().shouldUseSurfaceView()) {
1084 return dialerCall.getVideoTech().createVideoCallScreenDelegate(this, videoCallScreen);
1085 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001086 return new VideoCallPresenter();
1087 }
1088
1089 public void onPrimaryCallStateChanged() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001090 Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
Eric Erfanianccca3152017-02-22 16:32:36 -08001091 showMainInCallFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001092 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001093 }
1094
linyuh7b86f562017-11-16 11:24:09 -08001095 public void showToastForWiFiToLteHandover(DialerCall call) {
1096 if (call.hasShownWiFiToLteHandoverToast()) {
1097 return;
1098 }
1099
1100 Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
1101 call.setHasShownWiFiToLteHandoverToast();
Eric Erfanianccca3152017-02-22 16:32:36 -08001102 }
1103
linyuh7b86f562017-11-16 11:24:09 -08001104 public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
1105 if (call.showWifiHandoverAlertAsToast()) {
1106 Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
1107 .show();
1108 return;
1109 }
1110
1111 dismissPendingDialogs();
1112
1113 AlertDialog.Builder builder =
1114 new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
1115
1116 // This allows us to use the theme of the dialog instead of the activity
1117 View dialogCheckBoxView =
1118 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
1119 CheckBox wifiHandoverFailureCheckbox =
1120 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
1121 wifiHandoverFailureCheckbox.setChecked(false);
1122
1123 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
linyuhc3968e62017-11-20 17:40:50 -08001124 errorDialog =
linyuh7b86f562017-11-16 11:24:09 -08001125 builder
1126 .setView(dialogCheckBoxView)
1127 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
1128 .setOnCancelListener(dialogInterface -> onDialogDismissed())
1129 .setPositiveButton(
1130 android.R.string.ok,
1131 (dialogInterface, id) -> {
1132 call.setDoNotShowDialogForHandoffToWifiFailure(
1133 wifiHandoverFailureCheckbox.isChecked());
1134 dialogInterface.cancel();
1135 onDialogDismissed();
1136 })
1137 .setOnDismissListener(dialogInterface -> lock.release())
1138 .create();
linyuh7b86f562017-11-16 11:24:09 -08001139 errorDialog.show();
Eric Erfanianccca3152017-02-22 16:32:36 -08001140 }
1141
linyuh7b86f562017-11-16 11:24:09 -08001142 public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
1143 if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
1144 LogUtil.i(
1145 "InCallActivity.showDialogForInternationalCallOnWifi",
1146 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
1147 return;
1148 }
1149
1150 InternationalCallOnWifiDialogFragment fragment =
1151 InternationalCallOnWifiDialogFragment.newInstance(
linyuhc3968e62017-11-20 17:40:50 -08001152 call.getId(), internationalCallOnWifiCallback);
1153 fragment.show(getSupportFragmentManager(), Tags.INTERNATIONAL_CALL_ON_WIFI);
Eric Erfanianc857f902017-05-15 14:05:33 -07001154 }
1155
Eric Erfanian938468d2017-10-24 14:05:52 -07001156 @Override
1157 public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
1158 super.onMultiWindowModeChanged(isInMultiWindowMode);
linyuh57b093b2017-11-17 14:32:32 -08001159 updateNavigationBar(isDialpadVisible());
1160 }
1161
linyuhc3968e62017-11-20 17:40:50 -08001162 private void updateNavigationBar(boolean isDialpadVisible) {
linyuh57b093b2017-11-17 14:32:32 -08001163 if (ActivityCompat.isInMultiWindowMode(this)) {
1164 return;
1165 }
1166
1167 View navigationBarBackground = getWindow().findViewById(R.id.navigation_bar_background);
1168 if (navigationBarBackground != null) {
1169 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
Eric Erfanian938468d2017-10-24 14:05:52 -07001170 }
1171 }
1172
Eric Erfanianccca3152017-02-22 16:32:36 -08001173 public void setAllowOrientationChange(boolean allowOrientationChange) {
wangqi9982f0d2017-10-11 17:46:07 -07001174 if (this.allowOrientationChange == allowOrientationChange) {
1175 return;
1176 }
1177 this.allowOrientationChange = allowOrientationChange;
Eric Erfanianccca3152017-02-22 16:32:36 -08001178 if (!allowOrientationChange) {
1179 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_DISALLOW_ROTATION);
1180 } else {
1181 setRequestedOrientation(InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
1182 }
1183 enableInCallOrientationEventListener(allowOrientationChange);
1184 }
1185
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001186 public void hideMainInCallFragment() {
linyuhc3968e62017-11-20 17:40:50 -08001187 LogUtil.enterBlock("InCallActivity.hideMainInCallFragment");
1188 if (getCallCardFragmentVisible()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001189 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1190 hideInCallScreenFragment(transaction);
1191 hideVideoCallScreenFragment(transaction);
1192 transaction.commitAllowingStateLoss();
1193 getSupportFragmentManager().executePendingTransactions();
1194 }
1195 }
1196
1197 private void showMainInCallFragment() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001198 Trace.beginSection("InCallActivity.showMainInCallFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -08001199 // If the activity's onStart method hasn't been called yet then defer doing any work.
1200 if (!isVisible) {
1201 LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001202 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001203 return;
1204 }
1205
1206 // Don't let this be reentrant.
1207 if (isInShowMainInCallFragment) {
1208 LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
Eric Erfanian2ca43182017-08-31 06:57:16 -07001209 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001210 return;
1211 }
1212
1213 isInShowMainInCallFragment = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001214 ShouldShowUiResult shouldShowAnswerUi = getShouldShowAnswerUi();
1215 ShouldShowUiResult shouldShowVideoUi = getShouldShowVideoUi();
Eric Erfanianccca3152017-02-22 16:32:36 -08001216 LogUtil.i(
1217 "InCallActivity.showMainInCallFragment",
1218 "shouldShowAnswerUi: %b, shouldShowVideoUi: %b, "
1219 + "didShowAnswerScreen: %b, didShowInCallScreen: %b, didShowVideoCallScreen: %b",
1220 shouldShowAnswerUi.shouldShow,
Eric Erfanian10b34a52017-05-04 08:23:17 -07001221 shouldShowVideoUi.shouldShow,
Eric Erfanianccca3152017-02-22 16:32:36 -08001222 didShowAnswerScreen,
1223 didShowInCallScreen,
1224 didShowVideoCallScreen);
1225 // Only video call ui allows orientation change.
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001226 setAllowOrientationChange(shouldShowVideoUi.shouldShow);
Eric Erfanianccca3152017-02-22 16:32:36 -08001227
1228 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
1229 boolean didChangeInCall;
1230 boolean didChangeVideo;
1231 boolean didChangeAnswer;
1232 if (shouldShowAnswerUi.shouldShow) {
1233 didChangeInCall = hideInCallScreenFragment(transaction);
1234 didChangeVideo = hideVideoCallScreenFragment(transaction);
1235 didChangeAnswer = showAnswerScreenFragment(transaction, shouldShowAnswerUi.call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001236 } else if (shouldShowVideoUi.shouldShow) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001237 didChangeInCall = hideInCallScreenFragment(transaction);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001238 didChangeVideo = showVideoCallScreenFragment(transaction, shouldShowVideoUi.call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001239 didChangeAnswer = hideAnswerScreenFragment(transaction);
1240 } else {
1241 didChangeInCall = showInCallScreenFragment(transaction);
1242 didChangeVideo = hideVideoCallScreenFragment(transaction);
1243 didChangeAnswer = hideAnswerScreenFragment(transaction);
1244 }
1245
1246 if (didChangeInCall || didChangeVideo || didChangeAnswer) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001247 Trace.beginSection("InCallActivity.commitTransaction");
Eric Erfanianccca3152017-02-22 16:32:36 -08001248 transaction.commitNow();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001249 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001250 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1251 }
1252 isInShowMainInCallFragment = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001253 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -08001254 }
1255
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001256 private ShouldShowUiResult getShouldShowAnswerUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001257 DialerCall call = CallList.getInstance().getIncomingCall();
1258 if (call != null) {
1259 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001260 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001261 }
1262
1263 call = CallList.getInstance().getVideoUpgradeRequestCall();
1264 if (call != null) {
1265 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found video upgrade request");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001266 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001267 }
1268
1269 // Check if we're showing the answer screen and the call is disconnected. If this condition is
1270 // true then we won't switch from the answer UI to the in call UI. This prevents flicker when
1271 // the user rejects an incoming call.
1272 call = CallList.getInstance().getFirstCall();
1273 if (call == null) {
1274 call = CallList.getInstance().getBackgroundCall();
1275 }
1276 if (didShowAnswerScreen && (call == null || call.getState() == State.DISCONNECTED)) {
1277 LogUtil.i("InCallActivity.getShouldShowAnswerUi", "found disconnecting incoming call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001278 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001279 }
1280
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001281 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001282 }
1283
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001284 private static ShouldShowUiResult getShouldShowVideoUi() {
Eric Erfanianccca3152017-02-22 16:32:36 -08001285 DialerCall call = CallList.getInstance().getFirstCall();
1286 if (call == null) {
1287 LogUtil.i("InCallActivity.getShouldShowVideoUi", "null call");
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 if (call.isVideoCall()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001292 LogUtil.i("InCallActivity.getShouldShowVideoUi", "found video call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001293 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001294 }
1295
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001296 if (call.hasSentVideoUpgradeRequest()) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001297 LogUtil.i("InCallActivity.getShouldShowVideoUi", "upgrading to video");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001298 return new ShouldShowUiResult(true, call);
Eric Erfanianccca3152017-02-22 16:32:36 -08001299 }
1300
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001301 return new ShouldShowUiResult(false, null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001302 }
1303
1304 private boolean showAnswerScreenFragment(FragmentTransaction transaction, DialerCall call) {
1305 // When rejecting a call the active call can become null in which case we should continue
1306 // showing the answer screen.
1307 if (didShowAnswerScreen && call == null) {
1308 return false;
1309 }
1310
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001311 Assert.checkArgument(call != null, "didShowAnswerScreen was false but call was still null");
1312
1313 boolean isVideoUpgradeRequest = call.hasReceivedVideoUpgradeRequest();
Eric Erfanianccca3152017-02-22 16:32:36 -08001314
1315 // Check if we're already showing an answer screen for this call.
1316 if (didShowAnswerScreen) {
1317 AnswerScreen answerScreen = getAnswerScreen();
1318 if (answerScreen.getCallId().equals(call.getId())
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001319 && answerScreen.isVideoCall() == call.isVideoCall()
Eric Erfanian2ca43182017-08-31 06:57:16 -07001320 && answerScreen.isVideoUpgradeRequest() == isVideoUpgradeRequest
1321 && !answerScreen.isActionTimeout()) {
1322 LogUtil.d(
1323 "InCallActivity.showAnswerScreenFragment",
1324 "answer fragment exists for same call and has NOT been accepted/rejected/timed out");
Eric Erfanianccca3152017-02-22 16:32:36 -08001325 return false;
1326 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001327 if (answerScreen.isActionTimeout()) {
1328 LogUtil.i(
1329 "InCallActivity.showAnswerScreenFragment",
1330 "answer fragment exists but has been accepted/rejected and timed out");
1331 } else {
1332 LogUtil.i(
1333 "InCallActivity.showAnswerScreenFragment",
1334 "answer fragment exists but arguments do not match");
1335 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001336 hideAnswerScreenFragment(transaction);
1337 }
1338
1339 // Show a new answer screen.
1340 AnswerScreen answerScreen =
Eric Erfanianfc37b022017-03-21 10:11:17 -07001341 AnswerBindings.createAnswerScreen(
1342 call.getId(),
1343 call.isVideoCall(),
1344 isVideoUpgradeRequest,
Eric Erfanian90508232017-03-24 09:31:16 -07001345 call.getVideoTech().isSelfManagedCamera(),
1346 shouldAllowAnswerAndRelease(call),
1347 CallList.getInstance().getBackgroundCall() != null);
linyuhc3968e62017-11-20 17:40:50 -08001348 transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001349
1350 Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this);
1351 didShowAnswerScreen = true;
1352 return true;
1353 }
1354
Eric Erfanian90508232017-03-24 09:31:16 -07001355 private boolean shouldAllowAnswerAndRelease(DialerCall call) {
1356 if (CallList.getInstance().getActiveCall() == null) {
1357 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "no active call");
1358 return false;
1359 }
1360 if (getSystemService(TelephonyManager.class).getPhoneType()
1361 == TelephonyManager.PHONE_TYPE_CDMA) {
1362 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "PHONE_TYPE_CDMA not supported");
1363 return false;
1364 }
1365 if (call.isVideoCall() || call.hasReceivedVideoUpgradeRequest()) {
1366 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "video call");
1367 return false;
1368 }
linyuhc3968e62017-11-20 17:40:50 -08001369 if (!ConfigProviderBindings.get(this)
1370 .getBoolean(ConfigNames.ANSWER_AND_RELEASE_ENABLED, true)) {
Eric Erfanian90508232017-03-24 09:31:16 -07001371 LogUtil.i("InCallActivity.shouldAllowAnswerAndRelease", "disabled by config");
1372 return false;
1373 }
1374
1375 return true;
1376 }
1377
Eric Erfanianccca3152017-02-22 16:32:36 -08001378 private boolean hideAnswerScreenFragment(FragmentTransaction transaction) {
1379 if (!didShowAnswerScreen) {
1380 return false;
1381 }
1382 AnswerScreen answerScreen = getAnswerScreen();
1383 if (answerScreen != null) {
1384 transaction.remove(answerScreen.getAnswerScreenFragment());
1385 }
1386
1387 didShowAnswerScreen = false;
1388 return true;
1389 }
1390
1391 private boolean showInCallScreenFragment(FragmentTransaction transaction) {
1392 if (didShowInCallScreen) {
1393 return false;
1394 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001395 InCallScreen inCallScreen = InCallBindings.createInCallScreen();
linyuhc3968e62017-11-20 17:40:50 -08001396 transaction.add(R.id.main, inCallScreen.getInCallScreenFragment(), Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001397 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1398 didShowInCallScreen = true;
1399 return true;
1400 }
1401
1402 private boolean hideInCallScreenFragment(FragmentTransaction transaction) {
1403 if (!didShowInCallScreen) {
1404 return false;
1405 }
1406 InCallScreen inCallScreen = getInCallScreen();
1407 if (inCallScreen != null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001408 transaction.remove(inCallScreen.getInCallScreenFragment());
Eric Erfanianccca3152017-02-22 16:32:36 -08001409 }
1410 didShowInCallScreen = false;
1411 return true;
1412 }
1413
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001414 private boolean showVideoCallScreenFragment(FragmentTransaction transaction, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001415 if (didShowVideoCallScreen) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001416 VideoCallScreen videoCallScreen = getVideoCallScreen();
1417 if (videoCallScreen.getCallId().equals(call.getId())) {
1418 return false;
1419 }
1420 LogUtil.i(
1421 "InCallActivity.showVideoCallScreenFragment",
1422 "video call fragment exists but arguments do not match");
1423 hideVideoCallScreenFragment(transaction);
Eric Erfanianccca3152017-02-22 16:32:36 -08001424 }
1425
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001426 LogUtil.i("InCallActivity.showVideoCallScreenFragment", "call: %s", call);
1427
Eric Erfanian90508232017-03-24 09:31:16 -07001428 VideoCallScreen videoCallScreen =
1429 VideoBindings.createVideoCallScreen(
1430 call.getId(), call.getVideoTech().shouldUseSurfaceView());
linyuhc3968e62017-11-20 17:40:50 -08001431 transaction.add(
1432 R.id.main, videoCallScreen.getVideoCallScreenFragment(), Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001433
1434 Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
1435 didShowVideoCallScreen = true;
1436 return true;
1437 }
1438
1439 private boolean hideVideoCallScreenFragment(FragmentTransaction transaction) {
1440 if (!didShowVideoCallScreen) {
1441 return false;
1442 }
1443 VideoCallScreen videoCallScreen = getVideoCallScreen();
1444 if (videoCallScreen != null) {
1445 transaction.remove(videoCallScreen.getVideoCallScreenFragment());
1446 }
1447 didShowVideoCallScreen = false;
1448 return true;
1449 }
1450
linyuhc3968e62017-11-20 17:40:50 -08001451 private AnswerScreen getAnswerScreen() {
1452 return (AnswerScreen) getSupportFragmentManager().findFragmentByTag(Tags.ANSWER_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001453 }
1454
linyuhc3968e62017-11-20 17:40:50 -08001455 private InCallScreen getInCallScreen() {
1456 return (InCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.IN_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001457 }
1458
linyuhc3968e62017-11-20 17:40:50 -08001459 private VideoCallScreen getVideoCallScreen() {
1460 return (VideoCallScreen) getSupportFragmentManager().findFragmentByTag(Tags.VIDEO_CALL_SCREEN);
Eric Erfanianccca3152017-02-22 16:32:36 -08001461 }
1462
1463 @Override
1464 public void onPseudoScreenStateChanged(boolean isOn) {
1465 LogUtil.i("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
1466 pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
1467 }
1468
1469 /**
1470 * For some touch related issue, turning off the screen can be faked by drawing a black view over
1471 * the activity. All touch events started when the screen is "off" is rejected.
1472 *
1473 * @see PseudoScreenState
1474 */
1475 @Override
1476 public boolean dispatchTouchEvent(MotionEvent event) {
1477 // Reject any gesture that started when the screen is in the fake off state.
1478 if (touchDownWhenPseudoScreenOff) {
1479 if (event.getAction() == MotionEvent.ACTION_UP) {
1480 touchDownWhenPseudoScreenOff = false;
1481 }
1482 return true;
1483 }
1484 // Reject all touch event when the screen is in the fake off state.
1485 if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
1486 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1487 touchDownWhenPseudoScreenOff = true;
1488 LogUtil.i("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
1489 }
1490 return true;
1491 }
1492 return super.dispatchTouchEvent(event);
1493 }
1494
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001495 private static class ShouldShowUiResult {
Eric Erfanianccca3152017-02-22 16:32:36 -08001496 public final boolean shouldShow;
1497 public final DialerCall call;
1498
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001499 ShouldShowUiResult(boolean shouldShow, DialerCall call) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001500 this.shouldShow = shouldShow;
1501 this.call = call;
1502 }
1503 }
linyuhc3968e62017-11-20 17:40:50 -08001504
1505 private static final class IntentExtraNames {
1506 static final String FOR_FULL_SCREEN = "InCallActivity.for_full_screen_intent";
1507 static final String NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
1508 static final String SHOW_DIALPAD = "InCallActivity.show_dialpad";
1509 }
1510
1511 private static final class KeysForSavedInstance {
1512 static final String DIALPAD_TEXT = "InCallActivity.dialpad_text";
1513 static final String DID_SHOW_ANSWER_SCREEN = "did_show_answer_screen";
1514 static final String DID_SHOW_IN_CALL_SCREEN = "did_show_in_call_screen";
1515 static final String DID_SHOW_VIDEO_CALL_SCREEN = "did_show_video_call_screen";
1516 }
1517
1518 /** Request codes for pending intents. */
1519 public static final class PendingIntentRequestCodes {
1520 static final int NON_FULL_SCREEN = 0;
1521 static final int FULL_SCREEN = 1;
1522 static final int BUBBLE = 2;
1523 }
1524
1525 private static final class Tags {
1526 static final String ANSWER_SCREEN = "tag_answer_screen";
1527 static final String DIALPAD_FRAGMENT = "tag_dialpad_fragment";
1528 static final String IN_CALL_SCREEN = "tag_in_call_screen";
1529 static final String INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
1530 static final String SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
1531 static final String VIDEO_CALL_SCREEN = "tag_video_call_screen";
1532 static final String POST_CHAR_DIALOG_FRAGMENT = "tag_post_char_dialog_fragment";
1533 }
1534
1535 private static final class ConfigNames {
1536 static final String ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
1537 }
1538
1539 private static final class InternationalCallOnWifiCallback
1540 implements InternationalCallOnWifiDialogFragment.Callback {
1541 private static final String TAG = InternationalCallOnWifiCallback.class.getCanonicalName();
1542
1543 @Override
1544 public void continueCall(@NonNull String callId) {
1545 LogUtil.i(TAG, "Continuing call with ID: %s", callId);
1546 }
1547
1548 @Override
1549 public void cancelCall(@NonNull String callId) {
1550 DialerCall call = CallList.getInstance().getCallById(callId);
1551 if (call == null) {
1552 LogUtil.i(TAG, "Call destroyed before the dialog is closed");
1553 return;
1554 }
1555
1556 LogUtil.i(TAG, "Disconnecting international call on WiFi");
1557 call.disconnect();
1558 }
1559 }
1560
1561 private static final class SelectPhoneAccountListener
1562 extends SelectPhoneAccountDialogFragment.SelectPhoneAccountListener {
1563 private static final String TAG = SelectPhoneAccountListener.class.getCanonicalName();
1564
1565 @Override
1566 public void onPhoneAccountSelected(
1567 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
1568 DialerCall call = CallList.getInstance().getCallById(callId);
1569 LogUtil.i(TAG, "Phone account select with call:\n%s", call);
1570
1571 if (call != null) {
1572 call.phoneAccountSelected(selectedAccountHandle, setDefault);
1573 }
1574 }
1575
1576 @Override
1577 public void onDialogDismissed(String callId) {
1578 DialerCall call = CallList.getInstance().getCallById(callId);
1579 LogUtil.i(TAG, "Disconnecting call:\n%s" + call);
1580
1581 if (call != null) {
1582 call.disconnect();
1583 }
1584 }
1585 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001586}