blob: e97e6a0018878ca103a03f33b1aaed45dbf5553a [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;
linyuh9a915fc2017-11-09 16:27:13 -080036import android.support.annotation.VisibleForTesting;
Eric Erfanianccca3152017-02-22 16:32:36 -080037import android.support.v4.app.Fragment;
38import android.support.v4.app.FragmentManager;
39import android.support.v4.app.FragmentTransaction;
40import android.support.v4.content.res.ResourcesCompat;
linyuh9a915fc2017-11-09 16:27:13 -080041import android.telecom.CallAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -080042import android.telecom.PhoneAccountHandle;
Eric Erfanianccca3152017-02-22 16:32:36 -080043import android.view.KeyEvent;
44import android.view.View;
45import android.view.Window;
46import android.view.WindowManager;
47import android.view.animation.Animation;
48import android.view.animation.AnimationUtils;
49import android.widget.CheckBox;
50import android.widget.Toast;
51import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
52import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
53import com.android.dialer.animation.AnimUtils;
54import com.android.dialer.animation.AnimationListenerAdapter;
55import com.android.dialer.common.LogUtil;
wangqifad3d872017-10-25 13:15:23 -070056import com.android.dialer.compat.ActivityCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080057import com.android.dialer.compat.CompatUtils;
58import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070059import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080060import com.android.dialer.util.ViewUtil;
Eric Erfanian8369df02017-05-03 10:27:13 -070061import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080062import com.android.incallui.call.CallList;
63import com.android.incallui.call.DialerCall;
64import com.android.incallui.call.DialerCall.State;
65import com.android.incallui.call.TelecomAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070066import com.android.incallui.disconnectdialog.DisconnectMessage;
twyen8efb4952017-10-06 16:35:54 -070067import com.android.incallui.incalluilock.InCallUiLock;
Eric Erfanianc857f902017-05-15 14:05:33 -070068import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
69import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
linyuh9a915fc2017-11-09 16:27:13 -080070import com.google.common.base.Optional;
Eric Erfanianccca3152017-02-22 16:32:36 -080071import java.lang.annotation.Retention;
72import java.lang.annotation.RetentionPolicy;
73import java.util.ArrayList;
74import java.util.List;
75
76/** Shared functionality between the new and old in call activity. */
77public class InCallActivityCommon {
78
79 private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
80 private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
81 private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
82 "InCallActivity.for_full_screen_intent";
83
84 private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
85
86 private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
87 private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
Eric Erfanianc857f902017-05-15 14:05:33 -070088 private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
Eric Erfanianccca3152017-02-22 16:32:36 -080089
90 @Retention(RetentionPolicy.SOURCE)
91 @IntDef({
92 DIALPAD_REQUEST_NONE,
93 DIALPAD_REQUEST_SHOW,
94 DIALPAD_REQUEST_HIDE,
95 })
96 @interface DialpadRequestType {}
97
98 private static final int DIALPAD_REQUEST_NONE = 1;
99 private static final int DIALPAD_REQUEST_SHOW = 2;
100 private static final int DIALPAD_REQUEST_HIDE = 3;
101
linyuh9a915fc2017-11-09 16:27:13 -0800102 private static Optional<Integer> audioRouteForTesting = Optional.absent();
103
Eric Erfanianccca3152017-02-22 16:32:36 -0800104 private final InCallActivity inCallActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -0800105 private boolean showPostCharWaitDialogOnResume;
106 private String showPostCharWaitDialogCallId;
107 private String showPostCharWaitDialogChars;
108 private Dialog dialog;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700109 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -0800110 private InCallOrientationEventListener inCallOrientationEventListener;
111 private Animation dialpadSlideInAnimation;
112 private Animation dialpadSlideOutAnimation;
113 private boolean animateDialpadOnShow;
114 private String dtmfTextToPreopulate;
115 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700116 // If activity is going to be recreated. This is usually happening in {@link onNewIntent}.
117 private boolean isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800118
Eric Erfanianc857f902017-05-15 14:05:33 -0700119 private final SelectPhoneAccountListener selectAccountListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800120 new SelectPhoneAccountListener() {
121 @Override
122 public void onPhoneAccountSelected(
123 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
124 DialerCall call = CallList.getInstance().getCallById(callId);
125 LogUtil.i(
126 "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
127 "call: " + call);
128 if (call != null) {
129 call.phoneAccountSelected(selectedAccountHandle, setDefault);
130 }
131 }
132
133 @Override
134 public void onDialogDismissed(String callId) {
135 DialerCall call = CallList.getInstance().getCallById(callId);
136 LogUtil.i(
137 "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
138 "disconnecting call: " + call);
139 if (call != null) {
140 call.disconnect();
141 }
142 }
143 };
144
Eric Erfanianc857f902017-05-15 14:05:33 -0700145 private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
146 new Callback() {
147 @Override
148 public void continueCall(@NonNull String callId) {
149 LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
150 }
151
152 @Override
153 public void cancelCall(@NonNull String callId) {
154 DialerCall call = CallList.getInstance().getCallById(callId);
155 if (call == null) {
156 LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
157 return;
158 }
159 LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
160 call.disconnect();
161 }
162 };
163
Eric Erfanianccca3152017-02-22 16:32:36 -0800164 public static void setIntentExtras(
165 Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
166 if (showDialpad) {
167 intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
168 }
169 intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
170 intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
171 }
172
173 public InCallActivityCommon(InCallActivity inCallActivity) {
174 this.inCallActivity = inCallActivity;
175 }
176
177 public void onCreate(Bundle icicle) {
linyuh9a915fc2017-11-09 16:27:13 -0800178 setWindowFlags();
Eric Erfanianccca3152017-02-22 16:32:36 -0800179
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() {
wangqi9982f0d2017-10-11 17:46:07 -0700259 Trace.beginSection("InCallActivityCommon.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800260 // setting activity should be last thing in setup process
261 InCallPresenter.getInstance().setActivity(inCallActivity);
262 enableInCallOrientationEventListener(
263 inCallActivity.getRequestedOrientation()
264 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
265
266 InCallPresenter.getInstance().onActivityStarted();
yueg7b28abc2017-09-21 16:08:30 -0700267 if (!isRecreating) {
268 InCallPresenter.getInstance().onUiShowing(true);
269 }
wangqi9982f0d2017-10-11 17:46:07 -0700270 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800271 }
272
273 public void onResume() {
wangqi9982f0d2017-10-11 17:46:07 -0700274 Trace.beginSection("InCallActivityCommon.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800275 if (InCallPresenter.getInstance().isReadyForTearDown()) {
276 LogUtil.i(
277 "InCallActivityCommon.onResume",
278 "InCallPresenter is ready for tear down, not sending updates");
279 } else {
280 updateTaskDescription();
Eric Erfanianccca3152017-02-22 16:32:36 -0800281 }
282
283 // If there is a pending request to show or hide the dialpad, handle that now.
284 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
285 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
286 // Exit fullscreen so that the user has access to the dialpad hide/show button and
287 // can hide the dialpad. Important when showing the dialpad from within dialer.
288 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
289
290 inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
291 animateDialpadOnShow = false;
292
293 DialpadFragment dialpadFragment = getDialpadFragment();
294 if (dialpadFragment != null) {
295 dialpadFragment.setDtmfText(dtmfTextToPreopulate);
296 dtmfTextToPreopulate = null;
297 }
298 } else {
299 LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
300 if (getDialpadFragment() != null) {
301 inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
302 }
303 }
304 showDialpadRequest = DIALPAD_REQUEST_NONE;
305 }
Eric Erfanian938468d2017-10-24 14:05:52 -0700306 updateNavigationBar(isDialpadVisible());
Eric Erfanianccca3152017-02-22 16:32:36 -0800307
308 if (showPostCharWaitDialogOnResume) {
309 showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
310 }
311
312 CallList.getInstance()
313 .onInCallUiShown(
314 inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
wangqi9982f0d2017-10-11 17:46:07 -0700315 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800316 }
317
318 // onPause is guaranteed to be called when the InCallActivity goes
319 // in the background.
320 public void onPause() {
321 DialpadFragment dialpadFragment = getDialpadFragment();
322 if (dialpadFragment != null) {
323 dialpadFragment.onDialerKeyUp(null);
324 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800325 }
326
327 public void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700328 // Disconnects call waiting for account when activity is hidden e.g. user press home button.
329 // This is necessary otherwise the pending call will stuck on account choose and no new call
Eric Erfanian938468d2017-10-24 14:05:52 -0700330 // will be able to create. See a bug for more details.
Eric Erfanian2ca43182017-08-31 06:57:16 -0700331 // Skip this on locked screen since the activity may go over life cycle and start again.
332 if (!isRecreating
333 && !inCallActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
334 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
335 if (waitingForAccountCall != null) {
336 waitingForAccountCall.disconnect();
337 }
338 }
339
Eric Erfanianccca3152017-02-22 16:32:36 -0800340 enableInCallOrientationEventListener(false);
341 InCallPresenter.getInstance().updateIsChangingConfigurations();
342 InCallPresenter.getInstance().onActivityStopped();
yueg7b28abc2017-09-21 16:08:30 -0700343 if (!isRecreating) {
344 InCallPresenter.getInstance().onUiShowing(false);
twyen8efb4952017-10-06 16:35:54 -0700345 if (dialog != null) {
346 dialog.dismiss();
347 }
yueg7b28abc2017-09-21 16:08:30 -0700348 }
yueg345b0e12017-09-22 17:01:43 -0700349 if (inCallActivity.isFinishing()) {
350 InCallPresenter.getInstance().unsetActivity(inCallActivity);
351 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800352 }
353
354 public void onDestroy() {
355 InCallPresenter.getInstance().unsetActivity(inCallActivity);
356 InCallPresenter.getInstance().updateIsChangingConfigurations();
357 }
358
Eric Erfanian10b34a52017-05-04 08:23:17 -0700359 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800360 LogUtil.i("InCallActivityCommon.onNewIntent", "");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700361 this.isRecreating = isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800362
363 // We're being re-launched with a new Intent. Since it's possible for a
364 // single InCallActivity instance to persist indefinitely (even if we
365 // finish() ourselves), this sequence can potentially happen any time
366 // the InCallActivity needs to be displayed.
367
368 // Stash away the new intent so that we can get it in the future
369 // by calling getIntent(). (Otherwise getIntent() will return the
370 // original Intent from when we first got created!)
371 inCallActivity.setIntent(intent);
372
373 // Activities are always paused before receiving a new intent, so
374 // we can count on our onResume() method being called next.
375
376 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700377 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
378 if (!isRecreating) {
379 internalResolveIntent(intent);
380 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800381 }
382
383 public boolean onBackPressed(boolean isInCallScreenVisible) {
384 LogUtil.i("InCallActivityCommon.onBackPressed", "");
385
386 // BACK is also used to exit out of any "special modes" of the
387 // in-call UI:
388 if (!inCallActivity.isVisible()) {
389 return true;
390 }
391
392 if (!isInCallScreenVisible) {
393 return true;
394 }
395
396 DialpadFragment dialpadFragment = getDialpadFragment();
397 if (dialpadFragment != null && dialpadFragment.isVisible()) {
398 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
399 return true;
400 }
401
402 // Always disable the Back key while an incoming call is ringing
403 DialerCall call = CallList.getInstance().getIncomingCall();
404 if (call != null) {
405 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
406 return true;
407 }
408
409 // Nothing special to do. Fall back to the default behavior.
410 return false;
411 }
412
413 public boolean onKeyUp(int keyCode, KeyEvent event) {
414 DialpadFragment dialpadFragment = getDialpadFragment();
415 // push input to the dialer.
416 if (dialpadFragment != null
417 && (dialpadFragment.isVisible())
418 && (dialpadFragment.onDialerKeyUp(event))) {
419 return true;
420 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
421 // Always consume CALL to be sure the PhoneWindow won't do anything with it
422 return true;
423 }
424 return false;
425 }
426
427 public boolean onKeyDown(int keyCode, KeyEvent event) {
428 switch (keyCode) {
429 case KeyEvent.KEYCODE_CALL:
430 boolean handled = InCallPresenter.getInstance().handleCallKey();
431 if (!handled) {
432 LogUtil.e(
433 "InCallActivityCommon.onKeyDown",
434 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
435 }
436 // Always consume CALL to be sure the PhoneWindow won't do anything with it
437 return true;
438
439 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
440 // The standard system-wide handling of the ENDCALL key
441 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
442 // already implements exactly what the UI spec wants,
443 // namely (1) "hang up" if there's a current active call,
444 // or (2) "don't answer" if there's a current ringing call.
445
446 case KeyEvent.KEYCODE_CAMERA:
447 // Disable the CAMERA button while in-call since it's too
448 // easy to press accidentally.
449 return true;
450
451 case KeyEvent.KEYCODE_VOLUME_UP:
452 case KeyEvent.KEYCODE_VOLUME_DOWN:
453 case KeyEvent.KEYCODE_VOLUME_MUTE:
454 // Ringer silencing handled by PhoneWindowManager.
455 break;
456
457 case KeyEvent.KEYCODE_MUTE:
458 TelecomAdapter.getInstance()
459 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
460 return true;
461
462 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
463 case KeyEvent.KEYCODE_SLASH:
464 if (LogUtil.isVerboseEnabled()) {
465 LogUtil.v(
466 "InCallActivityCommon.onKeyDown",
467 "----------- InCallActivity View dump --------------");
468 // Dump starting from the top-level view of the entire activity:
469 Window w = inCallActivity.getWindow();
470 View decorView = w.getDecorView();
471 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
472 return true;
473 }
474 break;
475 case KeyEvent.KEYCODE_EQUALS:
476 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700477 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800478 }
479
480 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
481 }
482
linyuh9a915fc2017-11-09 16:27:13 -0800483 private void setWindowFlags() {
484 // Allow the activity to be shown when the screen is locked and filter out touch events that are
485 // "too fat".
486 int flags =
487 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
488 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
489
490 // When the audio stream is not directed through Bluetooth, turn the screen on once the
491 // activity is shown.
492 final int audioRoute = getAudioRoute();
493 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH) {
494 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
495 }
496
497 inCallActivity.getWindow().addFlags(flags);
498 }
499
500 private static int getAudioRoute() {
501 if (audioRouteForTesting.isPresent()) {
502 return audioRouteForTesting.get();
503 }
504
505 return AudioModeProvider.getInstance().getAudioState().getRoute();
506 }
507
508 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
509 public static void setAudioRouteForTesting(int audioRoute) {
510 audioRouteForTesting = Optional.of(audioRoute);
511 }
512
Eric Erfanianccca3152017-02-22 16:32:36 -0800513 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
514 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
515
516 // As soon as the user starts typing valid dialable keys on the
517 // keyboard (presumably to type DTMF tones) we start passing the
518 // key events to the DTMFDialer's onDialerKeyDown.
519 DialpadFragment dialpadFragment = getDialpadFragment();
520 if (dialpadFragment != null && dialpadFragment.isVisible()) {
521 return dialpadFragment.onDialerKeyDown(event);
522 }
523
524 return false;
525 }
526
Eric Erfanianccca3152017-02-22 16:32:36 -0800527 public void showPostCharWaitDialog(String callId, String chars) {
528 if (inCallActivity.isVisible()) {
529 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
530 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
531
532 showPostCharWaitDialogOnResume = false;
533 showPostCharWaitDialogCallId = null;
534 showPostCharWaitDialogChars = null;
535 } else {
536 showPostCharWaitDialogOnResume = true;
537 showPostCharWaitDialogCallId = callId;
538 showPostCharWaitDialogChars = chars;
539 }
540 }
541
Eric Erfanian2ca43182017-08-31 06:57:16 -0700542 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800543 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700544 "InCallActivityCommon.maybeShowErrorDialogOnDisconnect",
545 "disconnect cause: %s",
546 disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800547
548 if (!inCallActivity.isFinishing()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700549 if (disconnectMessage.dialog != null) {
550 showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 }
552 }
553 }
554
555 /**
556 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
557 * be shown on launch.
558 *
559 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
560 * false} to indicate no change should be made to the dialpad visibility.
561 */
562 private void relaunchedFromDialer(boolean showDialpad) {
563 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
564 animateDialpadOnShow = true;
565
566 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
567 // If there's only one line in use, AND it's on hold, then we're sure the user
568 // wants to use the dialpad toward the exact line, so un-hold the holding line.
569 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
570 if (call != null && call.getState() == State.ONHOLD) {
571 call.unhold();
572 }
573 }
574 }
575
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700576 void dismissPendingDialogs() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 if (dialog != null) {
578 dialog.dismiss();
579 dialog = null;
580 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700581 if (selectPhoneAccountDialogFragment != null) {
582 selectPhoneAccountDialogFragment.dismiss();
583 selectPhoneAccountDialogFragment = null;
584 }
Eric Erfanianc857f902017-05-15 14:05:33 -0700585
586 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
587 (InternationalCallOnWifiDialogFragment)
588 inCallActivity
589 .getSupportFragmentManager()
590 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
591 if (internationalCallOnWifiFragment != null) {
592 LogUtil.i(
593 "InCallActivityCommon.dismissPendingDialogs",
594 "dismissing InternationalCallOnWifiDialogFragment");
595 internationalCallOnWifiFragment.dismiss();
596 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800597 }
598
Eric Erfanianccca3152017-02-22 16:32:36 -0800599 private void showErrorDialog(Dialog dialog, CharSequence message) {
600 LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
601 inCallActivity.dismissPendingDialogs();
602
603 // Show toast if apps is in background when dialog won't be visible.
604 if (!inCallActivity.isVisible()) {
605 Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
606 return;
607 }
608
609 this.dialog = dialog;
twyen8efb4952017-10-06 16:35:54 -0700610 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800611 dialog.setOnDismissListener(
612 new OnDismissListener() {
613 @Override
614 public void onDismiss(DialogInterface dialog) {
615 LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
twyen8efb4952017-10-06 16:35:54 -0700616 lock.release();
Eric Erfanianccca3152017-02-22 16:32:36 -0800617 onDialogDismissed();
618 }
619 });
620 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
621 dialog.show();
622 }
623
624 private void onDialogDismissed() {
625 dialog = null;
626 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800627 }
628
629 public void enableInCallOrientationEventListener(boolean enable) {
630 if (enable) {
631 inCallOrientationEventListener.enable(true);
632 } else {
633 inCallOrientationEventListener.disable();
634 }
635 }
636
637 public void setExcludeFromRecents(boolean exclude) {
638 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
639 int taskId = inCallActivity.getTaskId();
640 for (int i = 0; i < tasks.size(); i++) {
641 ActivityManager.AppTask task = tasks.get(i);
642 try {
643 if (task.getTaskInfo().id == taskId) {
644 task.setExcludeFromRecents(exclude);
645 }
646 } catch (RuntimeException e) {
647 LogUtil.e(
648 "InCallActivityCommon.setExcludeFromRecents",
649 "RuntimeException when excluding task from recents.",
650 e);
651 }
652 }
653 }
654
Eric Erfanianc857f902017-05-15 14:05:33 -0700655 void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
656 LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
657 if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
658 LogUtil.i(
659 "InCallActivityCommon.showInternationalCallOnWifiDialog",
660 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
661 return;
662 }
663
664 InternationalCallOnWifiDialogFragment fragment =
665 InternationalCallOnWifiDialogFragment.newInstance(
666 call.getId(), internationalCallOnWifiCallback);
667 fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
668 }
669
Eric Erfanianccca3152017-02-22 16:32:36 -0800670 public void showWifiToLteHandoverToast(DialerCall call) {
671 if (call.hasShownWiFiToLteHandoverToast()) {
672 return;
673 }
674 Toast.makeText(
675 inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
676 .show();
677 call.setHasShownWiFiToLteHandoverToast();
678 }
679
680 public void showWifiFailedDialog(final DialerCall call) {
681 if (call.showWifiHandoverAlertAsToast()) {
682 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
683 Toast.makeText(
684 inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
685 .show();
686 return;
687 }
688
689 dismissPendingDialogs();
690
691 AlertDialog.Builder builder =
692 new AlertDialog.Builder(inCallActivity)
693 .setTitle(R.string.video_call_lte_to_wifi_failed_title);
694
695 // This allows us to use the theme of the dialog instead of the activity
696 View dialogCheckBoxView =
697 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
698 final CheckBox wifiHandoverFailureCheckbox =
699 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
700 wifiHandoverFailureCheckbox.setChecked(false);
701
twyen8efb4952017-10-06 16:35:54 -0700702 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800703 dialog =
704 builder
705 .setView(dialogCheckBoxView)
706 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
707 .setOnCancelListener(
708 new OnCancelListener() {
709 @Override
710 public void onCancel(DialogInterface dialog) {
711 onDialogDismissed();
712 }
713 })
714 .setPositiveButton(
715 android.R.string.ok,
716 new DialogInterface.OnClickListener() {
717 @Override
718 public void onClick(DialogInterface dialog, int id) {
719 call.setDoNotShowDialogForHandoffToWifiFailure(
720 wifiHandoverFailureCheckbox.isChecked());
721 dialog.cancel();
722 onDialogDismissed();
723 }
724 })
twyen8efb4952017-10-06 16:35:54 -0700725 .setOnDismissListener((dialog) -> lock.release())
Eric Erfanianccca3152017-02-22 16:32:36 -0800726 .create();
727
728 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
729 dialog.show();
730 }
731
Eric Erfanian938468d2017-10-24 14:05:52 -0700732 void updateNavigationBar(boolean isDialpadVisible) {
wangqifad3d872017-10-25 13:15:23 -0700733 if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) {
Eric Erfanian938468d2017-10-24 14:05:52 -0700734 View navigationBarBackground =
735 inCallActivity.getWindow().findViewById(R.id.navigation_bar_background);
736 if (navigationBarBackground != null) {
737 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
738 }
739 }
740 }
741
Eric Erfanianccca3152017-02-22 16:32:36 -0800742 public boolean showDialpadFragment(boolean show, boolean animate) {
743 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
744 boolean isDialpadVisible = isDialpadVisible();
745 LogUtil.i(
746 "InCallActivityCommon.showDialpadFragment",
747 "show: %b, animate: %b, " + "isDialpadVisible: %b",
748 show,
749 animate,
750 isDialpadVisible);
751 if (show == isDialpadVisible) {
752 return false;
753 }
754
755 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
756 if (dialpadFragmentManager == null) {
757 LogUtil.i(
758 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
759 return false;
760 }
761
762 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
763 // the listener is fired after an animation finishes.
764 if (!animate) {
765 if (show) {
766 performShowDialpadFragment(dialpadFragmentManager);
767 } else {
768 performHideDialpadFragment();
769 }
770 } else {
771 if (show) {
772 performShowDialpadFragment(dialpadFragmentManager);
773 getDialpadFragment().animateShowDialpad();
774 }
775 getDialpadFragment()
776 .getView()
777 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
778 }
779
780 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
781 if (sensor != null) {
782 sensor.onDialpadVisible(show);
783 }
784 showDialpadRequest = DIALPAD_REQUEST_NONE;
785 return true;
786 }
787
788 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
789 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
790 DialpadFragment dialpadFragment = getDialpadFragment();
791 if (dialpadFragment == null) {
792 transaction.add(
793 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
794 } else {
795 transaction.show(dialpadFragment);
796 }
797
798 transaction.commitAllowingStateLoss();
799 dialpadFragmentManager.executePendingTransactions();
800
801 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
Eric Erfanian938468d2017-10-24 14:05:52 -0700802 updateNavigationBar(true /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800803 }
804
805 private void performHideDialpadFragment() {
806 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
807 if (fragmentManager == null) {
808 LogUtil.e(
809 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
810 return;
811 }
812
813 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
814 if (fragment != null) {
815 FragmentTransaction transaction = fragmentManager.beginTransaction();
816 transaction.hide(fragment);
817 transaction.commitAllowingStateLoss();
818 fragmentManager.executePendingTransactions();
819 }
Eric Erfanian938468d2017-10-24 14:05:52 -0700820 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 }
822
823 public boolean isDialpadVisible() {
824 DialpadFragment dialpadFragment = getDialpadFragment();
825 return dialpadFragment != null && dialpadFragment.isVisible();
826 }
827
828 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
829 @Nullable
830 private DialpadFragment getDialpadFragment() {
831 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
832 if (fragmentManager == null) {
833 return null;
834 }
835 return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
836 }
837
838 public void updateTaskDescription() {
839 Resources resources = inCallActivity.getResources();
840 int color;
841 if (resources.getBoolean(R.bool.is_layout_landscape)) {
842 color =
843 ResourcesCompat.getColor(
844 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
845 } else {
846 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
847 }
848
849 TaskDescription td =
850 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
851 inCallActivity.setTaskDescription(td);
852 }
853
854 public boolean hasPendingDialogs() {
855 return dialog != null;
856 }
857
858 private void internalResolveIntent(Intent intent) {
859 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
860 return;
861 }
862
863 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
864 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
865 // dialpad should be initially visible. If the extra isn't
866 // present at all, we just leave the dialpad in its previous state.
867 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
868 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
869
870 relaunchedFromDialer(showDialpad);
871 }
872
873 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
874 if (outgoingCall == null) {
875 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
876 }
877
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800879 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
880
881 // InCallActivity is responsible for disconnecting a new outgoing call if there
882 // is no way of making it (i.e. no valid call capable accounts).
883 // If the version is not MSIM compatible, then ignore this code.
884 if (CompatUtils.isMSIMCompatible()
885 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
886 LogUtil.i(
887 "InCallActivityCommon.internalResolveIntent",
888 "call with no valid accounts, disconnecting");
889 outgoingCall.disconnect();
890 }
891
linyuh9c327da2017-11-14 12:33:48 -0800892 inCallActivity.dismissKeyguard(true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800893 }
894
895 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700896 if (didShowAccountSelectionDialog) {
897 inCallActivity.hideMainInCallFragment();
898 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800899 }
900
901 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700902 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
903 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800904 return false;
905 }
906
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700907 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800908 List<PhoneAccountHandle> phoneAccountHandles;
909 if (extras != null) {
910 phoneAccountHandles =
911 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
912 } else {
913 phoneAccountHandles = new ArrayList<>();
914 }
915
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700916 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800917 SelectPhoneAccountDialogFragment.newInstance(
918 R.string.select_phone_account_for_calls,
919 true,
920 phoneAccountHandles,
921 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700922 waitingForAccountCall.getId());
923 selectPhoneAccountDialogFragment.show(
924 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800925 return true;
926 }
927}