blob: 9e6271f3e48bcc5d9780cc26bea5a214e0c9fb19 [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();
yueg7b28abc2017-09-21 16:08:30 -0700266 if (!isRecreating) {
267 InCallPresenter.getInstance().onUiShowing(true);
268 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800269 }
270
271 public void onResume() {
272 if (InCallPresenter.getInstance().isReadyForTearDown()) {
273 LogUtil.i(
274 "InCallActivityCommon.onResume",
275 "InCallPresenter is ready for tear down, not sending updates");
276 } else {
277 updateTaskDescription();
Eric Erfanianccca3152017-02-22 16:32:36 -0800278 }
279
280 // If there is a pending request to show or hide the dialpad, handle that now.
281 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
282 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
283 // Exit fullscreen so that the user has access to the dialpad hide/show button and
284 // can hide the dialpad. Important when showing the dialpad from within dialer.
285 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
286
287 inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
288 animateDialpadOnShow = false;
289
290 DialpadFragment dialpadFragment = getDialpadFragment();
291 if (dialpadFragment != null) {
292 dialpadFragment.setDtmfText(dtmfTextToPreopulate);
293 dtmfTextToPreopulate = null;
294 }
295 } else {
296 LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
297 if (getDialpadFragment() != null) {
298 inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
299 }
300 }
301 showDialpadRequest = DIALPAD_REQUEST_NONE;
302 }
303
304 if (showPostCharWaitDialogOnResume) {
305 showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
306 }
307
308 CallList.getInstance()
309 .onInCallUiShown(
310 inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
311 }
312
313 // onPause is guaranteed to be called when the InCallActivity goes
314 // in the background.
315 public void onPause() {
316 DialpadFragment dialpadFragment = getDialpadFragment();
317 if (dialpadFragment != null) {
318 dialpadFragment.onDialerKeyUp(null);
319 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800320 }
321
322 public void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700323 // Disconnects call waiting for account when activity is hidden e.g. user press home button.
324 // This is necessary otherwise the pending call will stuck on account choose and no new call
325 // will be able to create. See b/63600434 for more details.
326 // Skip this on locked screen since the activity may go over life cycle and start again.
327 if (!isRecreating
328 && !inCallActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
329 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
330 if (waitingForAccountCall != null) {
331 waitingForAccountCall.disconnect();
332 }
333 }
334
Eric Erfanianccca3152017-02-22 16:32:36 -0800335 enableInCallOrientationEventListener(false);
336 InCallPresenter.getInstance().updateIsChangingConfigurations();
337 InCallPresenter.getInstance().onActivityStopped();
yueg7b28abc2017-09-21 16:08:30 -0700338 if (!isRecreating) {
339 InCallPresenter.getInstance().onUiShowing(false);
340 }
yueg345b0e12017-09-22 17:01:43 -0700341 if (inCallActivity.isFinishing()) {
342 InCallPresenter.getInstance().unsetActivity(inCallActivity);
343 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800344 }
345
346 public void onDestroy() {
347 InCallPresenter.getInstance().unsetActivity(inCallActivity);
348 InCallPresenter.getInstance().updateIsChangingConfigurations();
349 }
350
Eric Erfanian10b34a52017-05-04 08:23:17 -0700351 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800352 LogUtil.i("InCallActivityCommon.onNewIntent", "");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700353 this.isRecreating = isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800354
355 // We're being re-launched with a new Intent. Since it's possible for a
356 // single InCallActivity instance to persist indefinitely (even if we
357 // finish() ourselves), this sequence can potentially happen any time
358 // the InCallActivity needs to be displayed.
359
360 // Stash away the new intent so that we can get it in the future
361 // by calling getIntent(). (Otherwise getIntent() will return the
362 // original Intent from when we first got created!)
363 inCallActivity.setIntent(intent);
364
365 // Activities are always paused before receiving a new intent, so
366 // we can count on our onResume() method being called next.
367
368 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700369 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
370 if (!isRecreating) {
371 internalResolveIntent(intent);
372 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800373 }
374
375 public boolean onBackPressed(boolean isInCallScreenVisible) {
376 LogUtil.i("InCallActivityCommon.onBackPressed", "");
377
378 // BACK is also used to exit out of any "special modes" of the
379 // in-call UI:
380 if (!inCallActivity.isVisible()) {
381 return true;
382 }
383
384 if (!isInCallScreenVisible) {
385 return true;
386 }
387
388 DialpadFragment dialpadFragment = getDialpadFragment();
389 if (dialpadFragment != null && dialpadFragment.isVisible()) {
390 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
391 return true;
392 }
393
394 // Always disable the Back key while an incoming call is ringing
395 DialerCall call = CallList.getInstance().getIncomingCall();
396 if (call != null) {
397 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
398 return true;
399 }
400
401 // Nothing special to do. Fall back to the default behavior.
402 return false;
403 }
404
405 public boolean onKeyUp(int keyCode, KeyEvent event) {
406 DialpadFragment dialpadFragment = getDialpadFragment();
407 // push input to the dialer.
408 if (dialpadFragment != null
409 && (dialpadFragment.isVisible())
410 && (dialpadFragment.onDialerKeyUp(event))) {
411 return true;
412 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
413 // Always consume CALL to be sure the PhoneWindow won't do anything with it
414 return true;
415 }
416 return false;
417 }
418
419 public boolean onKeyDown(int keyCode, KeyEvent event) {
420 switch (keyCode) {
421 case KeyEvent.KEYCODE_CALL:
422 boolean handled = InCallPresenter.getInstance().handleCallKey();
423 if (!handled) {
424 LogUtil.e(
425 "InCallActivityCommon.onKeyDown",
426 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
427 }
428 // Always consume CALL to be sure the PhoneWindow won't do anything with it
429 return true;
430
431 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
432 // The standard system-wide handling of the ENDCALL key
433 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
434 // already implements exactly what the UI spec wants,
435 // namely (1) "hang up" if there's a current active call,
436 // or (2) "don't answer" if there's a current ringing call.
437
438 case KeyEvent.KEYCODE_CAMERA:
439 // Disable the CAMERA button while in-call since it's too
440 // easy to press accidentally.
441 return true;
442
443 case KeyEvent.KEYCODE_VOLUME_UP:
444 case KeyEvent.KEYCODE_VOLUME_DOWN:
445 case KeyEvent.KEYCODE_VOLUME_MUTE:
446 // Ringer silencing handled by PhoneWindowManager.
447 break;
448
449 case KeyEvent.KEYCODE_MUTE:
450 TelecomAdapter.getInstance()
451 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
452 return true;
453
454 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
455 case KeyEvent.KEYCODE_SLASH:
456 if (LogUtil.isVerboseEnabled()) {
457 LogUtil.v(
458 "InCallActivityCommon.onKeyDown",
459 "----------- InCallActivity View dump --------------");
460 // Dump starting from the top-level view of the entire activity:
461 Window w = inCallActivity.getWindow();
462 View decorView = w.getDecorView();
463 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
464 return true;
465 }
466 break;
467 case KeyEvent.KEYCODE_EQUALS:
468 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700469 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800470 }
471
472 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
473 }
474
475 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
476 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
477
478 // As soon as the user starts typing valid dialable keys on the
479 // keyboard (presumably to type DTMF tones) we start passing the
480 // key events to the DTMFDialer's onDialerKeyDown.
481 DialpadFragment dialpadFragment = getDialpadFragment();
482 if (dialpadFragment != null && dialpadFragment.isVisible()) {
483 return dialpadFragment.onDialerKeyDown(event);
484 }
485
486 return false;
487 }
488
489 public void dismissKeyguard(boolean dismiss) {
490 if (dismissKeyguard == dismiss) {
491 return;
492 }
493 dismissKeyguard = dismiss;
494 if (dismiss) {
495 inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
496 } else {
497 inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
498 }
499 }
500
501 public void showPostCharWaitDialog(String callId, String chars) {
502 if (inCallActivity.isVisible()) {
503 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
504 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
505
506 showPostCharWaitDialogOnResume = false;
507 showPostCharWaitDialogCallId = null;
508 showPostCharWaitDialogChars = null;
509 } else {
510 showPostCharWaitDialogOnResume = true;
511 showPostCharWaitDialogCallId = callId;
512 showPostCharWaitDialogChars = chars;
513 }
514 }
515
Eric Erfanian2ca43182017-08-31 06:57:16 -0700516 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800517 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700518 "InCallActivityCommon.maybeShowErrorDialogOnDisconnect",
519 "disconnect cause: %s",
520 disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800521
522 if (!inCallActivity.isFinishing()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700523 if (disconnectMessage.dialog != null) {
524 showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800525 }
526 }
527 }
528
529 /**
530 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
531 * be shown on launch.
532 *
533 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
534 * false} to indicate no change should be made to the dialpad visibility.
535 */
536 private void relaunchedFromDialer(boolean showDialpad) {
537 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
538 animateDialpadOnShow = true;
539
540 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
541 // If there's only one line in use, AND it's on hold, then we're sure the user
542 // wants to use the dialpad toward the exact line, so un-hold the holding line.
543 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
544 if (call != null && call.getState() == State.ONHOLD) {
545 call.unhold();
546 }
547 }
548 }
549
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700550 void dismissPendingDialogs() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 if (dialog != null) {
552 dialog.dismiss();
553 dialog = null;
554 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700555 if (selectPhoneAccountDialogFragment != null) {
556 selectPhoneAccountDialogFragment.dismiss();
557 selectPhoneAccountDialogFragment = null;
558 }
Eric Erfanianc857f902017-05-15 14:05:33 -0700559
560 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
561 (InternationalCallOnWifiDialogFragment)
562 inCallActivity
563 .getSupportFragmentManager()
564 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
565 if (internationalCallOnWifiFragment != null) {
566 LogUtil.i(
567 "InCallActivityCommon.dismissPendingDialogs",
568 "dismissing InternationalCallOnWifiDialogFragment");
569 internationalCallOnWifiFragment.dismiss();
570 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800571 }
572
Eric Erfanianccca3152017-02-22 16:32:36 -0800573 private void showErrorDialog(Dialog dialog, CharSequence message) {
574 LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
575 inCallActivity.dismissPendingDialogs();
576
577 // Show toast if apps is in background when dialog won't be visible.
578 if (!inCallActivity.isVisible()) {
579 Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
580 return;
581 }
582
583 this.dialog = dialog;
584 dialog.setOnDismissListener(
585 new OnDismissListener() {
586 @Override
587 public void onDismiss(DialogInterface dialog) {
588 LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
589 onDialogDismissed();
590 }
591 });
592 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
593 dialog.show();
594 }
595
596 private void onDialogDismissed() {
597 dialog = null;
598 CallList.getInstance().onErrorDialogDismissed();
599 InCallPresenter.getInstance().onDismissDialog();
600 }
601
602 public void enableInCallOrientationEventListener(boolean enable) {
603 if (enable) {
604 inCallOrientationEventListener.enable(true);
605 } else {
606 inCallOrientationEventListener.disable();
607 }
608 }
609
610 public void setExcludeFromRecents(boolean exclude) {
611 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
612 int taskId = inCallActivity.getTaskId();
613 for (int i = 0; i < tasks.size(); i++) {
614 ActivityManager.AppTask task = tasks.get(i);
615 try {
616 if (task.getTaskInfo().id == taskId) {
617 task.setExcludeFromRecents(exclude);
618 }
619 } catch (RuntimeException e) {
620 LogUtil.e(
621 "InCallActivityCommon.setExcludeFromRecents",
622 "RuntimeException when excluding task from recents.",
623 e);
624 }
625 }
626 }
627
Eric Erfanianc857f902017-05-15 14:05:33 -0700628 void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
629 LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
630 if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
631 LogUtil.i(
632 "InCallActivityCommon.showInternationalCallOnWifiDialog",
633 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
634 return;
635 }
636
637 InternationalCallOnWifiDialogFragment fragment =
638 InternationalCallOnWifiDialogFragment.newInstance(
639 call.getId(), internationalCallOnWifiCallback);
640 fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
641 }
642
Eric Erfanianccca3152017-02-22 16:32:36 -0800643 public void showWifiToLteHandoverToast(DialerCall call) {
644 if (call.hasShownWiFiToLteHandoverToast()) {
645 return;
646 }
647 Toast.makeText(
648 inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
649 .show();
650 call.setHasShownWiFiToLteHandoverToast();
651 }
652
653 public void showWifiFailedDialog(final DialerCall call) {
654 if (call.showWifiHandoverAlertAsToast()) {
655 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
656 Toast.makeText(
657 inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
658 .show();
659 return;
660 }
661
662 dismissPendingDialogs();
663
664 AlertDialog.Builder builder =
665 new AlertDialog.Builder(inCallActivity)
666 .setTitle(R.string.video_call_lte_to_wifi_failed_title);
667
668 // This allows us to use the theme of the dialog instead of the activity
669 View dialogCheckBoxView =
670 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
671 final CheckBox wifiHandoverFailureCheckbox =
672 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
673 wifiHandoverFailureCheckbox.setChecked(false);
674
675 dialog =
676 builder
677 .setView(dialogCheckBoxView)
678 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
679 .setOnCancelListener(
680 new OnCancelListener() {
681 @Override
682 public void onCancel(DialogInterface dialog) {
683 onDialogDismissed();
684 }
685 })
686 .setPositiveButton(
687 android.R.string.ok,
688 new DialogInterface.OnClickListener() {
689 @Override
690 public void onClick(DialogInterface dialog, int id) {
691 call.setDoNotShowDialogForHandoffToWifiFailure(
692 wifiHandoverFailureCheckbox.isChecked());
693 dialog.cancel();
694 onDialogDismissed();
695 }
696 })
697 .create();
698
699 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
700 dialog.show();
701 }
702
703 public boolean showDialpadFragment(boolean show, boolean animate) {
704 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
705 boolean isDialpadVisible = isDialpadVisible();
706 LogUtil.i(
707 "InCallActivityCommon.showDialpadFragment",
708 "show: %b, animate: %b, " + "isDialpadVisible: %b",
709 show,
710 animate,
711 isDialpadVisible);
712 if (show == isDialpadVisible) {
713 return false;
714 }
715
716 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
717 if (dialpadFragmentManager == null) {
718 LogUtil.i(
719 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
720 return false;
721 }
722
723 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
724 // the listener is fired after an animation finishes.
725 if (!animate) {
726 if (show) {
727 performShowDialpadFragment(dialpadFragmentManager);
728 } else {
729 performHideDialpadFragment();
730 }
731 } else {
732 if (show) {
733 performShowDialpadFragment(dialpadFragmentManager);
734 getDialpadFragment().animateShowDialpad();
735 }
736 getDialpadFragment()
737 .getView()
738 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
739 }
740
741 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
742 if (sensor != null) {
743 sensor.onDialpadVisible(show);
744 }
745 showDialpadRequest = DIALPAD_REQUEST_NONE;
746 return true;
747 }
748
749 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
750 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
751 DialpadFragment dialpadFragment = getDialpadFragment();
752 if (dialpadFragment == null) {
753 transaction.add(
754 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
755 } else {
756 transaction.show(dialpadFragment);
757 }
758
759 transaction.commitAllowingStateLoss();
760 dialpadFragmentManager.executePendingTransactions();
761
762 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
763 }
764
765 private void performHideDialpadFragment() {
766 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
767 if (fragmentManager == null) {
768 LogUtil.e(
769 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
770 return;
771 }
772
773 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
774 if (fragment != null) {
775 FragmentTransaction transaction = fragmentManager.beginTransaction();
776 transaction.hide(fragment);
777 transaction.commitAllowingStateLoss();
778 fragmentManager.executePendingTransactions();
779 }
780 }
781
782 public boolean isDialpadVisible() {
783 DialpadFragment dialpadFragment = getDialpadFragment();
784 return dialpadFragment != null && dialpadFragment.isVisible();
785 }
786
787 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
788 @Nullable
789 private DialpadFragment getDialpadFragment() {
790 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
791 if (fragmentManager == null) {
792 return null;
793 }
794 return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
795 }
796
797 public void updateTaskDescription() {
798 Resources resources = inCallActivity.getResources();
799 int color;
800 if (resources.getBoolean(R.bool.is_layout_landscape)) {
801 color =
802 ResourcesCompat.getColor(
803 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
804 } else {
805 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
806 }
807
808 TaskDescription td =
809 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
810 inCallActivity.setTaskDescription(td);
811 }
812
813 public boolean hasPendingDialogs() {
814 return dialog != null;
815 }
816
817 private void internalResolveIntent(Intent intent) {
818 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
819 return;
820 }
821
822 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
823 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
824 // dialpad should be initially visible. If the extra isn't
825 // present at all, we just leave the dialpad in its previous state.
826 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
827 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
828
829 relaunchedFromDialer(showDialpad);
830 }
831
832 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
833 if (outgoingCall == null) {
834 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
835 }
836
Eric Erfanianccca3152017-02-22 16:32:36 -0800837 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800838 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
839
840 // InCallActivity is responsible for disconnecting a new outgoing call if there
841 // is no way of making it (i.e. no valid call capable accounts).
842 // If the version is not MSIM compatible, then ignore this code.
843 if (CompatUtils.isMSIMCompatible()
844 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
845 LogUtil.i(
846 "InCallActivityCommon.internalResolveIntent",
847 "call with no valid accounts, disconnecting");
848 outgoingCall.disconnect();
849 }
850
851 dismissKeyguard(true);
852 }
853
854 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700855 if (didShowAccountSelectionDialog) {
856 inCallActivity.hideMainInCallFragment();
857 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 }
859
860 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700861 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
862 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800863 return false;
864 }
865
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700866 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800867 List<PhoneAccountHandle> phoneAccountHandles;
868 if (extras != null) {
869 phoneAccountHandles =
870 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
871 } else {
872 phoneAccountHandles = new ArrayList<>();
873 }
874
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700875 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800876 SelectPhoneAccountDialogFragment.newInstance(
877 R.string.select_phone_account_for_calls,
878 true,
879 phoneAccountHandles,
880 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700881 waitingForAccountCall.getId());
882 selectPhoneAccountDialogFragment.show(
883 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800884 return true;
885 }
886}