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