blob: 4b0a3cd05f8b8f52ef0d9e254d5bc34407740551 [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 Erfanianccca3152017-02-22 16:32:36 -080024import android.content.Context;
25import 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;
39import android.telecom.DisconnectCause;
40import android.telecom.PhoneAccountHandle;
41import android.text.TextUtils;
42import android.util.Pair;
43import android.view.KeyEvent;
44import android.view.View;
45import android.view.Window;
46import android.view.WindowManager;
47import android.view.animation.Animation;
48import android.view.animation.AnimationUtils;
49import android.widget.CheckBox;
50import android.widget.Toast;
51import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
52import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
53import com.android.dialer.animation.AnimUtils;
54import com.android.dialer.animation.AnimationListenerAdapter;
55import com.android.dialer.common.LogUtil;
56import com.android.dialer.compat.CompatUtils;
57import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070058import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080059import com.android.dialer.util.ViewUtil;
Eric Erfanian8369df02017-05-03 10:27:13 -070060import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080061import com.android.incallui.call.CallList;
62import com.android.incallui.call.DialerCall;
63import com.android.incallui.call.DialerCall.State;
64import com.android.incallui.call.TelecomAdapter;
Eric Erfanianc857f902017-05-15 14:05:33 -070065import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
66import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
Eric Erfanianccca3152017-02-22 16:32:36 -080067import com.android.incallui.wifi.EnableWifiCallingPrompt;
68import java.lang.annotation.Retention;
69import java.lang.annotation.RetentionPolicy;
70import java.util.ArrayList;
71import java.util.List;
72
73/** Shared functionality between the new and old in call activity. */
74public class InCallActivityCommon {
75
76 private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
77 private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
78 private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
79 "InCallActivity.for_full_screen_intent";
80
81 private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
82
83 private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
84 private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
Eric Erfanianc857f902017-05-15 14:05:33 -070085 private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
Eric Erfanianccca3152017-02-22 16:32:36 -080086
87 @Retention(RetentionPolicy.SOURCE)
88 @IntDef({
89 DIALPAD_REQUEST_NONE,
90 DIALPAD_REQUEST_SHOW,
91 DIALPAD_REQUEST_HIDE,
92 })
93 @interface DialpadRequestType {}
94
95 private static final int DIALPAD_REQUEST_NONE = 1;
96 private static final int DIALPAD_REQUEST_SHOW = 2;
97 private static final int DIALPAD_REQUEST_HIDE = 3;
98
99 private final InCallActivity inCallActivity;
100 private boolean dismissKeyguard;
101 private boolean showPostCharWaitDialogOnResume;
102 private String showPostCharWaitDialogCallId;
103 private String showPostCharWaitDialogChars;
104 private Dialog dialog;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700105 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -0800106 private InCallOrientationEventListener inCallOrientationEventListener;
107 private Animation dialpadSlideInAnimation;
108 private Animation dialpadSlideOutAnimation;
109 private boolean animateDialpadOnShow;
110 private String dtmfTextToPreopulate;
111 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
112
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
216 if (icicle != null) {
217 // 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() {
326 enableInCallOrientationEventListener(false);
327 InCallPresenter.getInstance().updateIsChangingConfigurations();
328 InCallPresenter.getInstance().onActivityStopped();
329 }
330
331 public void onDestroy() {
332 InCallPresenter.getInstance().unsetActivity(inCallActivity);
333 InCallPresenter.getInstance().updateIsChangingConfigurations();
334 }
335
Eric Erfanian10b34a52017-05-04 08:23:17 -0700336 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800337 LogUtil.i("InCallActivityCommon.onNewIntent", "");
338
339 // We're being re-launched with a new Intent. Since it's possible for a
340 // single InCallActivity instance to persist indefinitely (even if we
341 // finish() ourselves), this sequence can potentially happen any time
342 // the InCallActivity needs to be displayed.
343
344 // Stash away the new intent so that we can get it in the future
345 // by calling getIntent(). (Otherwise getIntent() will return the
346 // original Intent from when we first got created!)
347 inCallActivity.setIntent(intent);
348
349 // Activities are always paused before receiving a new intent, so
350 // we can count on our onResume() method being called next.
351
352 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700353 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
354 if (!isRecreating) {
355 internalResolveIntent(intent);
356 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800357 }
358
359 public boolean onBackPressed(boolean isInCallScreenVisible) {
360 LogUtil.i("InCallActivityCommon.onBackPressed", "");
361
362 // BACK is also used to exit out of any "special modes" of the
363 // in-call UI:
364 if (!inCallActivity.isVisible()) {
365 return true;
366 }
367
368 if (!isInCallScreenVisible) {
369 return true;
370 }
371
372 DialpadFragment dialpadFragment = getDialpadFragment();
373 if (dialpadFragment != null && dialpadFragment.isVisible()) {
374 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
375 return true;
376 }
377
378 // Always disable the Back key while an incoming call is ringing
379 DialerCall call = CallList.getInstance().getIncomingCall();
380 if (call != null) {
381 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
382 return true;
383 }
384
385 // Nothing special to do. Fall back to the default behavior.
386 return false;
387 }
388
389 public boolean onKeyUp(int keyCode, KeyEvent event) {
390 DialpadFragment dialpadFragment = getDialpadFragment();
391 // push input to the dialer.
392 if (dialpadFragment != null
393 && (dialpadFragment.isVisible())
394 && (dialpadFragment.onDialerKeyUp(event))) {
395 return true;
396 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
397 // Always consume CALL to be sure the PhoneWindow won't do anything with it
398 return true;
399 }
400 return false;
401 }
402
403 public boolean onKeyDown(int keyCode, KeyEvent event) {
404 switch (keyCode) {
405 case KeyEvent.KEYCODE_CALL:
406 boolean handled = InCallPresenter.getInstance().handleCallKey();
407 if (!handled) {
408 LogUtil.e(
409 "InCallActivityCommon.onKeyDown",
410 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
411 }
412 // Always consume CALL to be sure the PhoneWindow won't do anything with it
413 return true;
414
415 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
416 // The standard system-wide handling of the ENDCALL key
417 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
418 // already implements exactly what the UI spec wants,
419 // namely (1) "hang up" if there's a current active call,
420 // or (2) "don't answer" if there's a current ringing call.
421
422 case KeyEvent.KEYCODE_CAMERA:
423 // Disable the CAMERA button while in-call since it's too
424 // easy to press accidentally.
425 return true;
426
427 case KeyEvent.KEYCODE_VOLUME_UP:
428 case KeyEvent.KEYCODE_VOLUME_DOWN:
429 case KeyEvent.KEYCODE_VOLUME_MUTE:
430 // Ringer silencing handled by PhoneWindowManager.
431 break;
432
433 case KeyEvent.KEYCODE_MUTE:
434 TelecomAdapter.getInstance()
435 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
436 return true;
437
438 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
439 case KeyEvent.KEYCODE_SLASH:
440 if (LogUtil.isVerboseEnabled()) {
441 LogUtil.v(
442 "InCallActivityCommon.onKeyDown",
443 "----------- InCallActivity View dump --------------");
444 // Dump starting from the top-level view of the entire activity:
445 Window w = inCallActivity.getWindow();
446 View decorView = w.getDecorView();
447 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
448 return true;
449 }
450 break;
451 case KeyEvent.KEYCODE_EQUALS:
452 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700453 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800454 }
455
456 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
457 }
458
459 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
460 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
461
462 // As soon as the user starts typing valid dialable keys on the
463 // keyboard (presumably to type DTMF tones) we start passing the
464 // key events to the DTMFDialer's onDialerKeyDown.
465 DialpadFragment dialpadFragment = getDialpadFragment();
466 if (dialpadFragment != null && dialpadFragment.isVisible()) {
467 return dialpadFragment.onDialerKeyDown(event);
468 }
469
470 return false;
471 }
472
473 public void dismissKeyguard(boolean dismiss) {
474 if (dismissKeyguard == dismiss) {
475 return;
476 }
477 dismissKeyguard = dismiss;
478 if (dismiss) {
479 inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
480 } else {
481 inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
482 }
483 }
484
485 public void showPostCharWaitDialog(String callId, String chars) {
486 if (inCallActivity.isVisible()) {
487 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
488 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
489
490 showPostCharWaitDialogOnResume = false;
491 showPostCharWaitDialogCallId = null;
492 showPostCharWaitDialogChars = null;
493 } else {
494 showPostCharWaitDialogOnResume = true;
495 showPostCharWaitDialogCallId = callId;
496 showPostCharWaitDialogChars = chars;
497 }
498 }
499
500 public void maybeShowErrorDialogOnDisconnect(DisconnectCause cause) {
501 LogUtil.i(
502 "InCallActivityCommon.maybeShowErrorDialogOnDisconnect", "disconnect cause: %s", cause);
503
504 if (!inCallActivity.isFinishing()) {
505 if (EnableWifiCallingPrompt.shouldShowPrompt(cause)) {
506 Pair<Dialog, CharSequence> pair =
507 EnableWifiCallingPrompt.createDialog(inCallActivity, cause);
508 showErrorDialog(pair.first, pair.second);
509 } else if (shouldShowDisconnectErrorDialog(cause)) {
510 Pair<Dialog, CharSequence> pair = getDisconnectErrorDialog(inCallActivity, cause);
511 showErrorDialog(pair.first, pair.second);
512 }
513 }
514 }
515
516 /**
517 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
518 * be shown on launch.
519 *
520 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
521 * false} to indicate no change should be made to the dialpad visibility.
522 */
523 private void relaunchedFromDialer(boolean showDialpad) {
524 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
525 animateDialpadOnShow = true;
526
527 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
528 // If there's only one line in use, AND it's on hold, then we're sure the user
529 // wants to use the dialpad toward the exact line, so un-hold the holding line.
530 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
531 if (call != null && call.getState() == State.ONHOLD) {
532 call.unhold();
533 }
534 }
535 }
536
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700537 void dismissPendingDialogs() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800538 if (dialog != null) {
539 dialog.dismiss();
540 dialog = null;
541 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700542 if (selectPhoneAccountDialogFragment != null) {
543 selectPhoneAccountDialogFragment.dismiss();
544 selectPhoneAccountDialogFragment = null;
545 }
Eric Erfanianc857f902017-05-15 14:05:33 -0700546
547 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
548 (InternationalCallOnWifiDialogFragment)
549 inCallActivity
550 .getSupportFragmentManager()
551 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
552 if (internationalCallOnWifiFragment != null) {
553 LogUtil.i(
554 "InCallActivityCommon.dismissPendingDialogs",
555 "dismissing InternationalCallOnWifiDialogFragment");
556 internationalCallOnWifiFragment.dismiss();
557 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800558 }
559
560 private static boolean shouldShowDisconnectErrorDialog(@NonNull DisconnectCause cause) {
561 return !TextUtils.isEmpty(cause.getDescription())
562 && (cause.getCode() == DisconnectCause.ERROR
563 || cause.getCode() == DisconnectCause.RESTRICTED);
564 }
565
566 private static Pair<Dialog, CharSequence> getDisconnectErrorDialog(
567 @NonNull Context context, @NonNull DisconnectCause cause) {
568 CharSequence message = cause.getDescription();
569 Dialog dialog =
570 new AlertDialog.Builder(context)
571 .setMessage(message)
572 .setPositiveButton(android.R.string.ok, null)
573 .create();
574 return new Pair<>(dialog, message);
575 }
576
577 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;
588 dialog.setOnDismissListener(
589 new OnDismissListener() {
590 @Override
591 public void onDismiss(DialogInterface dialog) {
592 LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
593 onDialogDismissed();
594 }
595 });
596 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
597 dialog.show();
598 }
599
600 private void onDialogDismissed() {
601 dialog = null;
602 CallList.getInstance().onErrorDialogDismissed();
603 InCallPresenter.getInstance().onDismissDialog();
604 }
605
606 public void enableInCallOrientationEventListener(boolean enable) {
607 if (enable) {
608 inCallOrientationEventListener.enable(true);
609 } else {
610 inCallOrientationEventListener.disable();
611 }
612 }
613
614 public void setExcludeFromRecents(boolean exclude) {
615 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
616 int taskId = inCallActivity.getTaskId();
617 for (int i = 0; i < tasks.size(); i++) {
618 ActivityManager.AppTask task = tasks.get(i);
619 try {
620 if (task.getTaskInfo().id == taskId) {
621 task.setExcludeFromRecents(exclude);
622 }
623 } catch (RuntimeException e) {
624 LogUtil.e(
625 "InCallActivityCommon.setExcludeFromRecents",
626 "RuntimeException when excluding task from recents.",
627 e);
628 }
629 }
630 }
631
Eric Erfanianc857f902017-05-15 14:05:33 -0700632 void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
633 LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
634 if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
635 LogUtil.i(
636 "InCallActivityCommon.showInternationalCallOnWifiDialog",
637 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
638 return;
639 }
640
641 InternationalCallOnWifiDialogFragment fragment =
642 InternationalCallOnWifiDialogFragment.newInstance(
643 call.getId(), internationalCallOnWifiCallback);
644 fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
645 }
646
Eric Erfanianccca3152017-02-22 16:32:36 -0800647 public void showWifiToLteHandoverToast(DialerCall call) {
648 if (call.hasShownWiFiToLteHandoverToast()) {
649 return;
650 }
651 Toast.makeText(
652 inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
653 .show();
654 call.setHasShownWiFiToLteHandoverToast();
655 }
656
657 public void showWifiFailedDialog(final DialerCall call) {
658 if (call.showWifiHandoverAlertAsToast()) {
659 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
660 Toast.makeText(
661 inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
662 .show();
663 return;
664 }
665
666 dismissPendingDialogs();
667
668 AlertDialog.Builder builder =
669 new AlertDialog.Builder(inCallActivity)
670 .setTitle(R.string.video_call_lte_to_wifi_failed_title);
671
672 // This allows us to use the theme of the dialog instead of the activity
673 View dialogCheckBoxView =
674 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
675 final CheckBox wifiHandoverFailureCheckbox =
676 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
677 wifiHandoverFailureCheckbox.setChecked(false);
678
679 dialog =
680 builder
681 .setView(dialogCheckBoxView)
682 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
683 .setOnCancelListener(
684 new OnCancelListener() {
685 @Override
686 public void onCancel(DialogInterface dialog) {
687 onDialogDismissed();
688 }
689 })
690 .setPositiveButton(
691 android.R.string.ok,
692 new DialogInterface.OnClickListener() {
693 @Override
694 public void onClick(DialogInterface dialog, int id) {
695 call.setDoNotShowDialogForHandoffToWifiFailure(
696 wifiHandoverFailureCheckbox.isChecked());
697 dialog.cancel();
698 onDialogDismissed();
699 }
700 })
701 .create();
702
703 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
704 dialog.show();
705 }
706
707 public boolean showDialpadFragment(boolean show, boolean animate) {
708 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
709 boolean isDialpadVisible = isDialpadVisible();
710 LogUtil.i(
711 "InCallActivityCommon.showDialpadFragment",
712 "show: %b, animate: %b, " + "isDialpadVisible: %b",
713 show,
714 animate,
715 isDialpadVisible);
716 if (show == isDialpadVisible) {
717 return false;
718 }
719
720 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
721 if (dialpadFragmentManager == null) {
722 LogUtil.i(
723 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
724 return false;
725 }
726
727 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
728 // the listener is fired after an animation finishes.
729 if (!animate) {
730 if (show) {
731 performShowDialpadFragment(dialpadFragmentManager);
732 } else {
733 performHideDialpadFragment();
734 }
735 } else {
736 if (show) {
737 performShowDialpadFragment(dialpadFragmentManager);
738 getDialpadFragment().animateShowDialpad();
739 }
740 getDialpadFragment()
741 .getView()
742 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
743 }
744
745 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
746 if (sensor != null) {
747 sensor.onDialpadVisible(show);
748 }
749 showDialpadRequest = DIALPAD_REQUEST_NONE;
750 return true;
751 }
752
753 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
754 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
755 DialpadFragment dialpadFragment = getDialpadFragment();
756 if (dialpadFragment == null) {
757 transaction.add(
758 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
759 } else {
760 transaction.show(dialpadFragment);
761 }
762
763 transaction.commitAllowingStateLoss();
764 dialpadFragmentManager.executePendingTransactions();
765
766 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
767 }
768
769 private void performHideDialpadFragment() {
770 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
771 if (fragmentManager == null) {
772 LogUtil.e(
773 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
774 return;
775 }
776
777 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
778 if (fragment != null) {
779 FragmentTransaction transaction = fragmentManager.beginTransaction();
780 transaction.hide(fragment);
781 transaction.commitAllowingStateLoss();
782 fragmentManager.executePendingTransactions();
783 }
784 }
785
786 public boolean isDialpadVisible() {
787 DialpadFragment dialpadFragment = getDialpadFragment();
788 return dialpadFragment != null && dialpadFragment.isVisible();
789 }
790
791 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
792 @Nullable
793 private DialpadFragment getDialpadFragment() {
794 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
795 if (fragmentManager == null) {
796 return null;
797 }
798 return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
799 }
800
801 public void updateTaskDescription() {
802 Resources resources = inCallActivity.getResources();
803 int color;
804 if (resources.getBoolean(R.bool.is_layout_landscape)) {
805 color =
806 ResourcesCompat.getColor(
807 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
808 } else {
809 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
810 }
811
812 TaskDescription td =
813 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
814 inCallActivity.setTaskDescription(td);
815 }
816
817 public boolean hasPendingDialogs() {
818 return dialog != null;
819 }
820
821 private void internalResolveIntent(Intent intent) {
822 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
823 return;
824 }
825
826 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
827 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
828 // dialpad should be initially visible. If the extra isn't
829 // present at all, we just leave the dialpad in its previous state.
830 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
831 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
832
833 relaunchedFromDialer(showDialpad);
834 }
835
836 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
837 if (outgoingCall == null) {
838 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
839 }
840
Eric Erfanianccca3152017-02-22 16:32:36 -0800841 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800842 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
843
844 // InCallActivity is responsible for disconnecting a new outgoing call if there
845 // is no way of making it (i.e. no valid call capable accounts).
846 // If the version is not MSIM compatible, then ignore this code.
847 if (CompatUtils.isMSIMCompatible()
848 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
849 LogUtil.i(
850 "InCallActivityCommon.internalResolveIntent",
851 "call with no valid accounts, disconnecting");
852 outgoingCall.disconnect();
853 }
854
855 dismissKeyguard(true);
856 }
857
858 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700859 if (didShowAccountSelectionDialog) {
860 inCallActivity.hideMainInCallFragment();
861 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 }
863
864 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700865 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
866 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800867 return false;
868 }
869
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700870 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800871 List<PhoneAccountHandle> phoneAccountHandles;
872 if (extras != null) {
873 phoneAccountHandles =
874 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
875 } else {
876 phoneAccountHandles = new ArrayList<>();
877 }
878
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700879 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800880 SelectPhoneAccountDialogFragment.newInstance(
881 R.string.select_phone_account_for_calls,
882 true,
883 phoneAccountHandles,
884 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700885 waitingForAccountCall.getId());
886 selectPhoneAccountDialogFragment.show(
887 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800888 return true;
889 }
890}