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