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