blob: 9ccda32510af8f9288e59e6f051e8ffa39bd6095 [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
19import android.app.ActivityManager;
20import android.app.ActivityManager.AppTask;
21import android.app.ActivityManager.TaskDescription;
22import android.app.AlertDialog;
23import android.app.Dialog;
Eric Erfanian2ca43182017-08-31 06:57:16 -070024import android.app.KeyguardManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.content.DialogInterface;
26import android.content.DialogInterface.OnCancelListener;
27import android.content.DialogInterface.OnDismissListener;
28import android.content.Intent;
29import android.content.res.Configuration;
30import android.content.res.Resources;
31import android.os.Bundle;
32import android.support.annotation.IntDef;
33import android.support.annotation.NonNull;
34import android.support.annotation.Nullable;
35import android.support.v4.app.Fragment;
36import android.support.v4.app.FragmentManager;
37import android.support.v4.app.FragmentTransaction;
38import android.support.v4.content.res.ResourcesCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080039import android.telecom.PhoneAccountHandle;
Eric Erfanianccca3152017-02-22 16:32:36 -080040import android.view.KeyEvent;
41import android.view.View;
42import android.view.Window;
43import android.view.WindowManager;
44import android.view.animation.Animation;
45import android.view.animation.AnimationUtils;
46import android.widget.CheckBox;
47import android.widget.Toast;
48import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
49import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
50import com.android.dialer.animation.AnimUtils;
51import com.android.dialer.animation.AnimationListenerAdapter;
52import com.android.dialer.common.LogUtil;
53import com.android.dialer.compat.CompatUtils;
54import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070055import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080056import com.android.dialer.util.ViewUtil;
Eric Erfanian8369df02017-05-03 10:27:13 -070057import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080058import com.android.incallui.call.CallList;
59import com.android.incallui.call.DialerCall;
60import com.android.incallui.call.DialerCall.State;
61import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070062import com.android.incallui.disconnectdialog.DisconnectMessage;
twyen8efb4952017-10-06 16:35:54 -070063import com.android.incallui.incalluilock.InCallUiLock;
Eric Erfanianc857f902017-05-15 14:05:33 -070064import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
65import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
Eric Erfanianccca3152017-02-22 16:32:36 -080066import java.lang.annotation.Retention;
67import java.lang.annotation.RetentionPolicy;
68import java.util.ArrayList;
69import java.util.List;
70
71/** Shared functionality between the new and old in call activity. */
72public class InCallActivityCommon {
73
74 private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
75 private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
76 private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
77 "InCallActivity.for_full_screen_intent";
78
79 private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
80
81 private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
82 private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
Eric Erfanianc857f902017-05-15 14:05:33 -070083 private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
Eric Erfanianccca3152017-02-22 16:32:36 -080084
85 @Retention(RetentionPolicy.SOURCE)
86 @IntDef({
87 DIALPAD_REQUEST_NONE,
88 DIALPAD_REQUEST_SHOW,
89 DIALPAD_REQUEST_HIDE,
90 })
91 @interface DialpadRequestType {}
92
93 private static final int DIALPAD_REQUEST_NONE = 1;
94 private static final int DIALPAD_REQUEST_SHOW = 2;
95 private static final int DIALPAD_REQUEST_HIDE = 3;
96
97 private final InCallActivity inCallActivity;
98 private boolean dismissKeyguard;
99 private boolean showPostCharWaitDialogOnResume;
100 private String showPostCharWaitDialogCallId;
101 private String showPostCharWaitDialogChars;
102 private Dialog dialog;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700103 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -0800104 private InCallOrientationEventListener inCallOrientationEventListener;
105 private Animation dialpadSlideInAnimation;
106 private Animation dialpadSlideOutAnimation;
107 private boolean animateDialpadOnShow;
108 private String dtmfTextToPreopulate;
109 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700110 // If activity is going to be recreated. This is usually happening in {@link onNewIntent}.
111 private boolean isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800112
Eric Erfanianc857f902017-05-15 14:05:33 -0700113 private final SelectPhoneAccountListener selectAccountListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800114 new SelectPhoneAccountListener() {
115 @Override
116 public void onPhoneAccountSelected(
117 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
118 DialerCall call = CallList.getInstance().getCallById(callId);
119 LogUtil.i(
120 "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
121 "call: " + call);
122 if (call != null) {
123 call.phoneAccountSelected(selectedAccountHandle, setDefault);
124 }
125 }
126
127 @Override
128 public void onDialogDismissed(String callId) {
129 DialerCall call = CallList.getInstance().getCallById(callId);
130 LogUtil.i(
131 "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
132 "disconnecting call: " + call);
133 if (call != null) {
134 call.disconnect();
135 }
136 }
137 };
138
Eric Erfanianc857f902017-05-15 14:05:33 -0700139 private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
140 new Callback() {
141 @Override
142 public void continueCall(@NonNull String callId) {
143 LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
144 }
145
146 @Override
147 public void cancelCall(@NonNull String callId) {
148 DialerCall call = CallList.getInstance().getCallById(callId);
149 if (call == null) {
150 LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
151 return;
152 }
153 LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
154 call.disconnect();
155 }
156 };
157
Eric Erfanianccca3152017-02-22 16:32:36 -0800158 public static void setIntentExtras(
159 Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
160 if (showDialpad) {
161 intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
162 }
163 intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
164 intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
165 }
166
167 public InCallActivityCommon(InCallActivity inCallActivity) {
168 this.inCallActivity = inCallActivity;
169 }
170
171 public void onCreate(Bundle icicle) {
172 // set this flag so this activity will stay in front of the keyguard
173 // Have the WindowManager filter out touch events that are "too fat".
174 int flags =
175 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
176 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
177 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
178
179 inCallActivity.getWindow().addFlags(flags);
180
181 inCallActivity.setContentView(R.layout.incall_screen);
182
183 internalResolveIntent(inCallActivity.getIntent());
184
185 boolean isLandscape =
186 inCallActivity.getResources().getConfiguration().orientation
187 == Configuration.ORIENTATION_LANDSCAPE;
188 boolean isRtl = ViewUtil.isRtl();
189
190 if (isLandscape) {
191 dialpadSlideInAnimation =
192 AnimationUtils.loadAnimation(
193 inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
194 dialpadSlideOutAnimation =
195 AnimationUtils.loadAnimation(
196 inCallActivity,
197 isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
198 } else {
199 dialpadSlideInAnimation =
200 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
201 dialpadSlideOutAnimation =
202 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
203 }
204
205 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
206 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
207
208 dialpadSlideOutAnimation.setAnimationListener(
209 new AnimationListenerAdapter() {
210 @Override
211 public void onAnimationEnd(Animation animation) {
212 performHideDialpadFragment();
213 }
214 });
215
Eric Erfanian2ca43182017-08-31 06:57:16 -0700216 // Don't override the value if show dialpad request is true in intent extras.
217 if (icicle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 // If the dialpad was shown before, set variables indicating it should be shown and
219 // populated with the previous DTMF text. The dialpad is actually shown and populated
220 // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
221 if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
222 boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
223 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
224 animateDialpadOnShow = false;
225 }
226 dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
227
228 SelectPhoneAccountDialogFragment dialogFragment =
229 (SelectPhoneAccountDialogFragment)
230 inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
231 if (dialogFragment != null) {
232 dialogFragment.setListener(selectAccountListener);
233 }
234 }
235
Eric Erfanianc857f902017-05-15 14:05:33 -0700236 InternationalCallOnWifiDialogFragment existingInternationalFragment =
237 (InternationalCallOnWifiDialogFragment)
238 inCallActivity
239 .getSupportFragmentManager()
240 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
241 if (existingInternationalFragment != null) {
242 LogUtil.i(
243 "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
244 existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
245 }
246
Eric Erfanianccca3152017-02-22 16:32:36 -0800247 inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity);
248 }
249
250 public void onSaveInstanceState(Bundle out) {
251 // TODO: The dialpad fragment should handle this as part of its own state
252 out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
253 DialpadFragment dialpadFragment = getDialpadFragment();
254 if (dialpadFragment != null) {
255 out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
256 }
257 }
258
259 public void onStart() {
260 // setting activity should be last thing in setup process
261 InCallPresenter.getInstance().setActivity(inCallActivity);
262 enableInCallOrientationEventListener(
263 inCallActivity.getRequestedOrientation()
264 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
265
266 InCallPresenter.getInstance().onActivityStarted();
yueg7b28abc2017-09-21 16:08:30 -0700267 if (!isRecreating) {
268 InCallPresenter.getInstance().onUiShowing(true);
269 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800270 }
271
272 public void onResume() {
273 if (InCallPresenter.getInstance().isReadyForTearDown()) {
274 LogUtil.i(
275 "InCallActivityCommon.onResume",
276 "InCallPresenter is ready for tear down, not sending updates");
277 } else {
278 updateTaskDescription();
Eric Erfanianccca3152017-02-22 16:32:36 -0800279 }
280
281 // If there is a pending request to show or hide the dialpad, handle that now.
282 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
283 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
284 // Exit fullscreen so that the user has access to the dialpad hide/show button and
285 // can hide the dialpad. Important when showing the dialpad from within dialer.
286 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
287
288 inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
289 animateDialpadOnShow = false;
290
291 DialpadFragment dialpadFragment = getDialpadFragment();
292 if (dialpadFragment != null) {
293 dialpadFragment.setDtmfText(dtmfTextToPreopulate);
294 dtmfTextToPreopulate = null;
295 }
296 } else {
297 LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
298 if (getDialpadFragment() != null) {
299 inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
300 }
301 }
302 showDialpadRequest = DIALPAD_REQUEST_NONE;
303 }
304
305 if (showPostCharWaitDialogOnResume) {
306 showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
307 }
308
309 CallList.getInstance()
310 .onInCallUiShown(
311 inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
312 }
313
314 // onPause is guaranteed to be called when the InCallActivity goes
315 // in the background.
316 public void onPause() {
317 DialpadFragment dialpadFragment = getDialpadFragment();
318 if (dialpadFragment != null) {
319 dialpadFragment.onDialerKeyUp(null);
320 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800321 }
322
323 public void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700324 // Disconnects call waiting for account when activity is hidden e.g. user press home button.
325 // This is necessary otherwise the pending call will stuck on account choose and no new call
326 // will be able to create. See b/63600434 for more details.
327 // Skip this on locked screen since the activity may go over life cycle and start again.
328 if (!isRecreating
329 && !inCallActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
330 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
331 if (waitingForAccountCall != null) {
332 waitingForAccountCall.disconnect();
333 }
334 }
335
Eric Erfanianccca3152017-02-22 16:32:36 -0800336 enableInCallOrientationEventListener(false);
337 InCallPresenter.getInstance().updateIsChangingConfigurations();
338 InCallPresenter.getInstance().onActivityStopped();
yueg7b28abc2017-09-21 16:08:30 -0700339 if (!isRecreating) {
340 InCallPresenter.getInstance().onUiShowing(false);
twyen8efb4952017-10-06 16:35:54 -0700341 if (dialog != null) {
342 dialog.dismiss();
343 }
yueg7b28abc2017-09-21 16:08:30 -0700344 }
yueg345b0e12017-09-22 17:01:43 -0700345 if (inCallActivity.isFinishing()) {
346 InCallPresenter.getInstance().unsetActivity(inCallActivity);
347 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800348 }
349
350 public void onDestroy() {
351 InCallPresenter.getInstance().unsetActivity(inCallActivity);
352 InCallPresenter.getInstance().updateIsChangingConfigurations();
353 }
354
Eric Erfanian10b34a52017-05-04 08:23:17 -0700355 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800356 LogUtil.i("InCallActivityCommon.onNewIntent", "");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700357 this.isRecreating = isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800358
359 // We're being re-launched with a new Intent. Since it's possible for a
360 // single InCallActivity instance to persist indefinitely (even if we
361 // finish() ourselves), this sequence can potentially happen any time
362 // the InCallActivity needs to be displayed.
363
364 // Stash away the new intent so that we can get it in the future
365 // by calling getIntent(). (Otherwise getIntent() will return the
366 // original Intent from when we first got created!)
367 inCallActivity.setIntent(intent);
368
369 // Activities are always paused before receiving a new intent, so
370 // we can count on our onResume() method being called next.
371
372 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700373 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
374 if (!isRecreating) {
375 internalResolveIntent(intent);
376 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800377 }
378
379 public boolean onBackPressed(boolean isInCallScreenVisible) {
380 LogUtil.i("InCallActivityCommon.onBackPressed", "");
381
382 // BACK is also used to exit out of any "special modes" of the
383 // in-call UI:
384 if (!inCallActivity.isVisible()) {
385 return true;
386 }
387
388 if (!isInCallScreenVisible) {
389 return true;
390 }
391
392 DialpadFragment dialpadFragment = getDialpadFragment();
393 if (dialpadFragment != null && dialpadFragment.isVisible()) {
394 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
395 return true;
396 }
397
398 // Always disable the Back key while an incoming call is ringing
399 DialerCall call = CallList.getInstance().getIncomingCall();
400 if (call != null) {
401 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
402 return true;
403 }
404
405 // Nothing special to do. Fall back to the default behavior.
406 return false;
407 }
408
409 public boolean onKeyUp(int keyCode, KeyEvent event) {
410 DialpadFragment dialpadFragment = getDialpadFragment();
411 // push input to the dialer.
412 if (dialpadFragment != null
413 && (dialpadFragment.isVisible())
414 && (dialpadFragment.onDialerKeyUp(event))) {
415 return true;
416 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
417 // Always consume CALL to be sure the PhoneWindow won't do anything with it
418 return true;
419 }
420 return false;
421 }
422
423 public boolean onKeyDown(int keyCode, KeyEvent event) {
424 switch (keyCode) {
425 case KeyEvent.KEYCODE_CALL:
426 boolean handled = InCallPresenter.getInstance().handleCallKey();
427 if (!handled) {
428 LogUtil.e(
429 "InCallActivityCommon.onKeyDown",
430 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
431 }
432 // Always consume CALL to be sure the PhoneWindow won't do anything with it
433 return true;
434
435 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
436 // The standard system-wide handling of the ENDCALL key
437 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
438 // already implements exactly what the UI spec wants,
439 // namely (1) "hang up" if there's a current active call,
440 // or (2) "don't answer" if there's a current ringing call.
441
442 case KeyEvent.KEYCODE_CAMERA:
443 // Disable the CAMERA button while in-call since it's too
444 // easy to press accidentally.
445 return true;
446
447 case KeyEvent.KEYCODE_VOLUME_UP:
448 case KeyEvent.KEYCODE_VOLUME_DOWN:
449 case KeyEvent.KEYCODE_VOLUME_MUTE:
450 // Ringer silencing handled by PhoneWindowManager.
451 break;
452
453 case KeyEvent.KEYCODE_MUTE:
454 TelecomAdapter.getInstance()
455 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
456 return true;
457
458 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
459 case KeyEvent.KEYCODE_SLASH:
460 if (LogUtil.isVerboseEnabled()) {
461 LogUtil.v(
462 "InCallActivityCommon.onKeyDown",
463 "----------- InCallActivity View dump --------------");
464 // Dump starting from the top-level view of the entire activity:
465 Window w = inCallActivity.getWindow();
466 View decorView = w.getDecorView();
467 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
468 return true;
469 }
470 break;
471 case KeyEvent.KEYCODE_EQUALS:
472 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700473 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800474 }
475
476 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
477 }
478
479 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
480 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
481
482 // As soon as the user starts typing valid dialable keys on the
483 // keyboard (presumably to type DTMF tones) we start passing the
484 // key events to the DTMFDialer's onDialerKeyDown.
485 DialpadFragment dialpadFragment = getDialpadFragment();
486 if (dialpadFragment != null && dialpadFragment.isVisible()) {
487 return dialpadFragment.onDialerKeyDown(event);
488 }
489
490 return false;
491 }
492
493 public void dismissKeyguard(boolean dismiss) {
494 if (dismissKeyguard == dismiss) {
495 return;
496 }
497 dismissKeyguard = dismiss;
498 if (dismiss) {
499 inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
500 } else {
501 inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
502 }
503 }
504
505 public void showPostCharWaitDialog(String callId, String chars) {
506 if (inCallActivity.isVisible()) {
507 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
508 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
509
510 showPostCharWaitDialogOnResume = false;
511 showPostCharWaitDialogCallId = null;
512 showPostCharWaitDialogChars = null;
513 } else {
514 showPostCharWaitDialogOnResume = true;
515 showPostCharWaitDialogCallId = callId;
516 showPostCharWaitDialogChars = chars;
517 }
518 }
519
Eric Erfanian2ca43182017-08-31 06:57:16 -0700520 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800521 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700522 "InCallActivityCommon.maybeShowErrorDialogOnDisconnect",
523 "disconnect cause: %s",
524 disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800525
526 if (!inCallActivity.isFinishing()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700527 if (disconnectMessage.dialog != null) {
528 showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800529 }
530 }
531 }
532
533 /**
534 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
535 * be shown on launch.
536 *
537 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
538 * false} to indicate no change should be made to the dialpad visibility.
539 */
540 private void relaunchedFromDialer(boolean showDialpad) {
541 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
542 animateDialpadOnShow = true;
543
544 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
545 // If there's only one line in use, AND it's on hold, then we're sure the user
546 // wants to use the dialpad toward the exact line, so un-hold the holding line.
547 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
548 if (call != null && call.getState() == State.ONHOLD) {
549 call.unhold();
550 }
551 }
552 }
553
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700554 void dismissPendingDialogs() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800555 if (dialog != null) {
556 dialog.dismiss();
557 dialog = null;
558 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700559 if (selectPhoneAccountDialogFragment != null) {
560 selectPhoneAccountDialogFragment.dismiss();
561 selectPhoneAccountDialogFragment = null;
562 }
Eric Erfanianc857f902017-05-15 14:05:33 -0700563
564 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
565 (InternationalCallOnWifiDialogFragment)
566 inCallActivity
567 .getSupportFragmentManager()
568 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
569 if (internationalCallOnWifiFragment != null) {
570 LogUtil.i(
571 "InCallActivityCommon.dismissPendingDialogs",
572 "dismissing InternationalCallOnWifiDialogFragment");
573 internationalCallOnWifiFragment.dismiss();
574 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 }
576
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 private void showErrorDialog(Dialog dialog, CharSequence message) {
578 LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
579 inCallActivity.dismissPendingDialogs();
580
581 // Show toast if apps is in background when dialog won't be visible.
582 if (!inCallActivity.isVisible()) {
583 Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
584 return;
585 }
586
587 this.dialog = dialog;
twyen8efb4952017-10-06 16:35:54 -0700588 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800589 dialog.setOnDismissListener(
590 new OnDismissListener() {
591 @Override
592 public void onDismiss(DialogInterface dialog) {
593 LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
twyen8efb4952017-10-06 16:35:54 -0700594 lock.release();
Eric Erfanianccca3152017-02-22 16:32:36 -0800595 onDialogDismissed();
596 }
597 });
598 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
599 dialog.show();
600 }
601
602 private void onDialogDismissed() {
603 dialog = null;
604 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800605 }
606
607 public void enableInCallOrientationEventListener(boolean enable) {
608 if (enable) {
609 inCallOrientationEventListener.enable(true);
610 } else {
611 inCallOrientationEventListener.disable();
612 }
613 }
614
615 public void setExcludeFromRecents(boolean exclude) {
616 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
617 int taskId = inCallActivity.getTaskId();
618 for (int i = 0; i < tasks.size(); i++) {
619 ActivityManager.AppTask task = tasks.get(i);
620 try {
621 if (task.getTaskInfo().id == taskId) {
622 task.setExcludeFromRecents(exclude);
623 }
624 } catch (RuntimeException e) {
625 LogUtil.e(
626 "InCallActivityCommon.setExcludeFromRecents",
627 "RuntimeException when excluding task from recents.",
628 e);
629 }
630 }
631 }
632
Eric Erfanianc857f902017-05-15 14:05:33 -0700633 void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
634 LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
635 if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
636 LogUtil.i(
637 "InCallActivityCommon.showInternationalCallOnWifiDialog",
638 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
639 return;
640 }
641
642 InternationalCallOnWifiDialogFragment fragment =
643 InternationalCallOnWifiDialogFragment.newInstance(
644 call.getId(), internationalCallOnWifiCallback);
645 fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
646 }
647
Eric Erfanianccca3152017-02-22 16:32:36 -0800648 public void showWifiToLteHandoverToast(DialerCall call) {
649 if (call.hasShownWiFiToLteHandoverToast()) {
650 return;
651 }
652 Toast.makeText(
653 inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
654 .show();
655 call.setHasShownWiFiToLteHandoverToast();
656 }
657
658 public void showWifiFailedDialog(final DialerCall call) {
659 if (call.showWifiHandoverAlertAsToast()) {
660 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
661 Toast.makeText(
662 inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
663 .show();
664 return;
665 }
666
667 dismissPendingDialogs();
668
669 AlertDialog.Builder builder =
670 new AlertDialog.Builder(inCallActivity)
671 .setTitle(R.string.video_call_lte_to_wifi_failed_title);
672
673 // This allows us to use the theme of the dialog instead of the activity
674 View dialogCheckBoxView =
675 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
676 final CheckBox wifiHandoverFailureCheckbox =
677 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
678 wifiHandoverFailureCheckbox.setChecked(false);
679
twyen8efb4952017-10-06 16:35:54 -0700680 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800681 dialog =
682 builder
683 .setView(dialogCheckBoxView)
684 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
685 .setOnCancelListener(
686 new OnCancelListener() {
687 @Override
688 public void onCancel(DialogInterface dialog) {
689 onDialogDismissed();
690 }
691 })
692 .setPositiveButton(
693 android.R.string.ok,
694 new DialogInterface.OnClickListener() {
695 @Override
696 public void onClick(DialogInterface dialog, int id) {
697 call.setDoNotShowDialogForHandoffToWifiFailure(
698 wifiHandoverFailureCheckbox.isChecked());
699 dialog.cancel();
700 onDialogDismissed();
701 }
702 })
twyen8efb4952017-10-06 16:35:54 -0700703 .setOnDismissListener((dialog) -> lock.release())
Eric Erfanianccca3152017-02-22 16:32:36 -0800704 .create();
705
706 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
707 dialog.show();
708 }
709
710 public boolean showDialpadFragment(boolean show, boolean animate) {
711 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
712 boolean isDialpadVisible = isDialpadVisible();
713 LogUtil.i(
714 "InCallActivityCommon.showDialpadFragment",
715 "show: %b, animate: %b, " + "isDialpadVisible: %b",
716 show,
717 animate,
718 isDialpadVisible);
719 if (show == isDialpadVisible) {
720 return false;
721 }
722
723 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
724 if (dialpadFragmentManager == null) {
725 LogUtil.i(
726 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
727 return false;
728 }
729
730 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
731 // the listener is fired after an animation finishes.
732 if (!animate) {
733 if (show) {
734 performShowDialpadFragment(dialpadFragmentManager);
735 } else {
736 performHideDialpadFragment();
737 }
738 } else {
739 if (show) {
740 performShowDialpadFragment(dialpadFragmentManager);
741 getDialpadFragment().animateShowDialpad();
742 }
743 getDialpadFragment()
744 .getView()
745 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
746 }
747
748 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
749 if (sensor != null) {
750 sensor.onDialpadVisible(show);
751 }
752 showDialpadRequest = DIALPAD_REQUEST_NONE;
753 return true;
754 }
755
756 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
757 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
758 DialpadFragment dialpadFragment = getDialpadFragment();
759 if (dialpadFragment == null) {
760 transaction.add(
761 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
762 } else {
763 transaction.show(dialpadFragment);
764 }
765
766 transaction.commitAllowingStateLoss();
767 dialpadFragmentManager.executePendingTransactions();
768
769 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
770 }
771
772 private void performHideDialpadFragment() {
773 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
774 if (fragmentManager == null) {
775 LogUtil.e(
776 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
777 return;
778 }
779
780 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
781 if (fragment != null) {
782 FragmentTransaction transaction = fragmentManager.beginTransaction();
783 transaction.hide(fragment);
784 transaction.commitAllowingStateLoss();
785 fragmentManager.executePendingTransactions();
786 }
787 }
788
789 public boolean isDialpadVisible() {
790 DialpadFragment dialpadFragment = getDialpadFragment();
791 return dialpadFragment != null && dialpadFragment.isVisible();
792 }
793
794 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
795 @Nullable
796 private DialpadFragment getDialpadFragment() {
797 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
798 if (fragmentManager == null) {
799 return null;
800 }
801 return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
802 }
803
804 public void updateTaskDescription() {
805 Resources resources = inCallActivity.getResources();
806 int color;
807 if (resources.getBoolean(R.bool.is_layout_landscape)) {
808 color =
809 ResourcesCompat.getColor(
810 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
811 } else {
812 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
813 }
814
815 TaskDescription td =
816 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
817 inCallActivity.setTaskDescription(td);
818 }
819
820 public boolean hasPendingDialogs() {
821 return dialog != null;
822 }
823
824 private void internalResolveIntent(Intent intent) {
825 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
826 return;
827 }
828
829 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
830 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
831 // dialpad should be initially visible. If the extra isn't
832 // present at all, we just leave the dialpad in its previous state.
833 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
834 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
835
836 relaunchedFromDialer(showDialpad);
837 }
838
839 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
840 if (outgoingCall == null) {
841 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
842 }
843
Eric Erfanianccca3152017-02-22 16:32:36 -0800844 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800845 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
846
847 // InCallActivity is responsible for disconnecting a new outgoing call if there
848 // is no way of making it (i.e. no valid call capable accounts).
849 // If the version is not MSIM compatible, then ignore this code.
850 if (CompatUtils.isMSIMCompatible()
851 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
852 LogUtil.i(
853 "InCallActivityCommon.internalResolveIntent",
854 "call with no valid accounts, disconnecting");
855 outgoingCall.disconnect();
856 }
857
858 dismissKeyguard(true);
859 }
860
861 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700862 if (didShowAccountSelectionDialog) {
863 inCallActivity.hideMainInCallFragment();
864 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800865 }
866
867 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700868 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
869 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800870 return false;
871 }
872
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700873 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800874 List<PhoneAccountHandle> phoneAccountHandles;
875 if (extras != null) {
876 phoneAccountHandles =
877 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
878 } else {
879 phoneAccountHandles = new ArrayList<>();
880 }
881
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700882 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800883 SelectPhoneAccountDialogFragment.newInstance(
884 R.string.select_phone_account_for_calls,
885 true,
886 phoneAccountHandles,
887 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700888 waitingForAccountCall.getId());
889 selectPhoneAccountDialogFragment.show(
890 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800891 return true;
892 }
893}