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