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