blob: 5a5d770d0680319161c21a0bbacfe4b3403e79fc [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;
105 private boolean dismissKeyguard;
106 private boolean showPostCharWaitDialogOnResume;
107 private String showPostCharWaitDialogCallId;
108 private String showPostCharWaitDialogChars;
109 private Dialog dialog;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700110 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -0800111 private InCallOrientationEventListener inCallOrientationEventListener;
112 private Animation dialpadSlideInAnimation;
113 private Animation dialpadSlideOutAnimation;
114 private boolean animateDialpadOnShow;
115 private String dtmfTextToPreopulate;
116 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700117 // If activity is going to be recreated. This is usually happening in {@link onNewIntent}.
118 private boolean isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800119
Eric Erfanianc857f902017-05-15 14:05:33 -0700120 private final SelectPhoneAccountListener selectAccountListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800121 new SelectPhoneAccountListener() {
122 @Override
123 public void onPhoneAccountSelected(
124 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
125 DialerCall call = CallList.getInstance().getCallById(callId);
126 LogUtil.i(
127 "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
128 "call: " + call);
129 if (call != null) {
130 call.phoneAccountSelected(selectedAccountHandle, setDefault);
131 }
132 }
133
134 @Override
135 public void onDialogDismissed(String callId) {
136 DialerCall call = CallList.getInstance().getCallById(callId);
137 LogUtil.i(
138 "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
139 "disconnecting call: " + call);
140 if (call != null) {
141 call.disconnect();
142 }
143 }
144 };
145
Eric Erfanianc857f902017-05-15 14:05:33 -0700146 private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
147 new Callback() {
148 @Override
149 public void continueCall(@NonNull String callId) {
150 LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
151 }
152
153 @Override
154 public void cancelCall(@NonNull String callId) {
155 DialerCall call = CallList.getInstance().getCallById(callId);
156 if (call == null) {
157 LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
158 return;
159 }
160 LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
161 call.disconnect();
162 }
163 };
164
Eric Erfanianccca3152017-02-22 16:32:36 -0800165 public static void setIntentExtras(
166 Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
167 if (showDialpad) {
168 intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
169 }
170 intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
171 intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
172 }
173
174 public InCallActivityCommon(InCallActivity inCallActivity) {
175 this.inCallActivity = inCallActivity;
176 }
177
178 public void onCreate(Bundle icicle) {
linyuh9a915fc2017-11-09 16:27:13 -0800179 setWindowFlags();
Eric Erfanianccca3152017-02-22 16:32:36 -0800180
181 inCallActivity.setContentView(R.layout.incall_screen);
182
183 internalResolveIntent(inCallActivity.getIntent());
184
185 boolean isLandscape =
186 inCallActivity.getResources().getConfiguration().orientation
187 == Configuration.ORIENTATION_LANDSCAPE;
188 boolean isRtl = ViewUtil.isRtl();
189
190 if (isLandscape) {
191 dialpadSlideInAnimation =
192 AnimationUtils.loadAnimation(
193 inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
194 dialpadSlideOutAnimation =
195 AnimationUtils.loadAnimation(
196 inCallActivity,
197 isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
198 } else {
199 dialpadSlideInAnimation =
200 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
201 dialpadSlideOutAnimation =
202 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
203 }
204
205 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
206 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
207
208 dialpadSlideOutAnimation.setAnimationListener(
209 new AnimationListenerAdapter() {
210 @Override
211 public void onAnimationEnd(Animation animation) {
212 performHideDialpadFragment();
213 }
214 });
215
Eric Erfanian2ca43182017-08-31 06:57:16 -0700216 // Don't override the value if show dialpad request is true in intent extras.
217 if (icicle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 // If the dialpad was shown before, set variables indicating it should be shown and
219 // populated with the previous DTMF text. The dialpad is actually shown and populated
220 // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
221 if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
222 boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
223 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
224 animateDialpadOnShow = false;
225 }
226 dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
227
228 SelectPhoneAccountDialogFragment dialogFragment =
229 (SelectPhoneAccountDialogFragment)
230 inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
231 if (dialogFragment != null) {
232 dialogFragment.setListener(selectAccountListener);
233 }
234 }
235
Eric Erfanianc857f902017-05-15 14:05:33 -0700236 InternationalCallOnWifiDialogFragment existingInternationalFragment =
237 (InternationalCallOnWifiDialogFragment)
238 inCallActivity
239 .getSupportFragmentManager()
240 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
241 if (existingInternationalFragment != null) {
242 LogUtil.i(
243 "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
244 existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
245 }
246
Eric Erfanianccca3152017-02-22 16:32:36 -0800247 inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity);
248 }
249
250 public void onSaveInstanceState(Bundle out) {
251 // TODO: The dialpad fragment should handle this as part of its own state
252 out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
253 DialpadFragment dialpadFragment = getDialpadFragment();
254 if (dialpadFragment != null) {
255 out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
256 }
257 }
258
259 public void onStart() {
wangqi9982f0d2017-10-11 17:46:07 -0700260 Trace.beginSection("InCallActivityCommon.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800261 // setting activity should be last thing in setup process
262 InCallPresenter.getInstance().setActivity(inCallActivity);
263 enableInCallOrientationEventListener(
264 inCallActivity.getRequestedOrientation()
265 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
266
267 InCallPresenter.getInstance().onActivityStarted();
yueg7b28abc2017-09-21 16:08:30 -0700268 if (!isRecreating) {
269 InCallPresenter.getInstance().onUiShowing(true);
270 }
wangqi9982f0d2017-10-11 17:46:07 -0700271 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800272 }
273
274 public void onResume() {
wangqi9982f0d2017-10-11 17:46:07 -0700275 Trace.beginSection("InCallActivityCommon.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800276 if (InCallPresenter.getInstance().isReadyForTearDown()) {
277 LogUtil.i(
278 "InCallActivityCommon.onResume",
279 "InCallPresenter is ready for tear down, not sending updates");
280 } else {
281 updateTaskDescription();
Eric Erfanianccca3152017-02-22 16:32:36 -0800282 }
283
284 // If there is a pending request to show or hide the dialpad, handle that now.
285 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
286 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
287 // Exit fullscreen so that the user has access to the dialpad hide/show button and
288 // can hide the dialpad. Important when showing the dialpad from within dialer.
289 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
290
291 inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
292 animateDialpadOnShow = false;
293
294 DialpadFragment dialpadFragment = getDialpadFragment();
295 if (dialpadFragment != null) {
296 dialpadFragment.setDtmfText(dtmfTextToPreopulate);
297 dtmfTextToPreopulate = null;
298 }
299 } else {
300 LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
301 if (getDialpadFragment() != null) {
302 inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
303 }
304 }
305 showDialpadRequest = DIALPAD_REQUEST_NONE;
306 }
Eric Erfanian938468d2017-10-24 14:05:52 -0700307 updateNavigationBar(isDialpadVisible());
Eric Erfanianccca3152017-02-22 16:32:36 -0800308
309 if (showPostCharWaitDialogOnResume) {
310 showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
311 }
312
313 CallList.getInstance()
314 .onInCallUiShown(
315 inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
wangqi9982f0d2017-10-11 17:46:07 -0700316 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800317 }
318
319 // onPause is guaranteed to be called when the InCallActivity goes
320 // in the background.
321 public void onPause() {
322 DialpadFragment dialpadFragment = getDialpadFragment();
323 if (dialpadFragment != null) {
324 dialpadFragment.onDialerKeyUp(null);
325 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800326 }
327
328 public void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700329 // Disconnects call waiting for account when activity is hidden e.g. user press home button.
330 // This is necessary otherwise the pending call will stuck on account choose and no new call
Eric Erfanian938468d2017-10-24 14:05:52 -0700331 // will be able to create. See a bug for more details.
Eric Erfanian2ca43182017-08-31 06:57:16 -0700332 // Skip this on locked screen since the activity may go over life cycle and start again.
333 if (!isRecreating
334 && !inCallActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
335 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
336 if (waitingForAccountCall != null) {
337 waitingForAccountCall.disconnect();
338 }
339 }
340
Eric Erfanianccca3152017-02-22 16:32:36 -0800341 enableInCallOrientationEventListener(false);
342 InCallPresenter.getInstance().updateIsChangingConfigurations();
343 InCallPresenter.getInstance().onActivityStopped();
yueg7b28abc2017-09-21 16:08:30 -0700344 if (!isRecreating) {
345 InCallPresenter.getInstance().onUiShowing(false);
twyen8efb4952017-10-06 16:35:54 -0700346 if (dialog != null) {
347 dialog.dismiss();
348 }
yueg7b28abc2017-09-21 16:08:30 -0700349 }
yueg345b0e12017-09-22 17:01:43 -0700350 if (inCallActivity.isFinishing()) {
351 InCallPresenter.getInstance().unsetActivity(inCallActivity);
352 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800353 }
354
355 public void onDestroy() {
356 InCallPresenter.getInstance().unsetActivity(inCallActivity);
357 InCallPresenter.getInstance().updateIsChangingConfigurations();
358 }
359
Eric Erfanian10b34a52017-05-04 08:23:17 -0700360 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800361 LogUtil.i("InCallActivityCommon.onNewIntent", "");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700362 this.isRecreating = isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800363
364 // We're being re-launched with a new Intent. Since it's possible for a
365 // single InCallActivity instance to persist indefinitely (even if we
366 // finish() ourselves), this sequence can potentially happen any time
367 // the InCallActivity needs to be displayed.
368
369 // Stash away the new intent so that we can get it in the future
370 // by calling getIntent(). (Otherwise getIntent() will return the
371 // original Intent from when we first got created!)
372 inCallActivity.setIntent(intent);
373
374 // Activities are always paused before receiving a new intent, so
375 // we can count on our onResume() method being called next.
376
377 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700378 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
379 if (!isRecreating) {
380 internalResolveIntent(intent);
381 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800382 }
383
384 public boolean onBackPressed(boolean isInCallScreenVisible) {
385 LogUtil.i("InCallActivityCommon.onBackPressed", "");
386
387 // BACK is also used to exit out of any "special modes" of the
388 // in-call UI:
389 if (!inCallActivity.isVisible()) {
390 return true;
391 }
392
393 if (!isInCallScreenVisible) {
394 return true;
395 }
396
397 DialpadFragment dialpadFragment = getDialpadFragment();
398 if (dialpadFragment != null && dialpadFragment.isVisible()) {
399 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
400 return true;
401 }
402
403 // Always disable the Back key while an incoming call is ringing
404 DialerCall call = CallList.getInstance().getIncomingCall();
405 if (call != null) {
406 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
407 return true;
408 }
409
410 // Nothing special to do. Fall back to the default behavior.
411 return false;
412 }
413
414 public boolean onKeyUp(int keyCode, KeyEvent event) {
415 DialpadFragment dialpadFragment = getDialpadFragment();
416 // push input to the dialer.
417 if (dialpadFragment != null
418 && (dialpadFragment.isVisible())
419 && (dialpadFragment.onDialerKeyUp(event))) {
420 return true;
421 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
422 // Always consume CALL to be sure the PhoneWindow won't do anything with it
423 return true;
424 }
425 return false;
426 }
427
428 public boolean onKeyDown(int keyCode, KeyEvent event) {
429 switch (keyCode) {
430 case KeyEvent.KEYCODE_CALL:
431 boolean handled = InCallPresenter.getInstance().handleCallKey();
432 if (!handled) {
433 LogUtil.e(
434 "InCallActivityCommon.onKeyDown",
435 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
436 }
437 // Always consume CALL to be sure the PhoneWindow won't do anything with it
438 return true;
439
440 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
441 // The standard system-wide handling of the ENDCALL key
442 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
443 // already implements exactly what the UI spec wants,
444 // namely (1) "hang up" if there's a current active call,
445 // or (2) "don't answer" if there's a current ringing call.
446
447 case KeyEvent.KEYCODE_CAMERA:
448 // Disable the CAMERA button while in-call since it's too
449 // easy to press accidentally.
450 return true;
451
452 case KeyEvent.KEYCODE_VOLUME_UP:
453 case KeyEvent.KEYCODE_VOLUME_DOWN:
454 case KeyEvent.KEYCODE_VOLUME_MUTE:
455 // Ringer silencing handled by PhoneWindowManager.
456 break;
457
458 case KeyEvent.KEYCODE_MUTE:
459 TelecomAdapter.getInstance()
460 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
461 return true;
462
463 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
464 case KeyEvent.KEYCODE_SLASH:
465 if (LogUtil.isVerboseEnabled()) {
466 LogUtil.v(
467 "InCallActivityCommon.onKeyDown",
468 "----------- InCallActivity View dump --------------");
469 // Dump starting from the top-level view of the entire activity:
470 Window w = inCallActivity.getWindow();
471 View decorView = w.getDecorView();
472 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
473 return true;
474 }
475 break;
476 case KeyEvent.KEYCODE_EQUALS:
477 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700478 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800479 }
480
481 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
482 }
483
linyuh9a915fc2017-11-09 16:27:13 -0800484 private void setWindowFlags() {
485 // Allow the activity to be shown when the screen is locked and filter out touch events that are
486 // "too fat".
487 int flags =
488 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
489 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
490
491 // When the audio stream is not directed through Bluetooth, turn the screen on once the
492 // activity is shown.
493 final int audioRoute = getAudioRoute();
494 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH) {
495 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
496 }
497
498 inCallActivity.getWindow().addFlags(flags);
499 }
500
501 private static int getAudioRoute() {
502 if (audioRouteForTesting.isPresent()) {
503 return audioRouteForTesting.get();
504 }
505
506 return AudioModeProvider.getInstance().getAudioState().getRoute();
507 }
508
509 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
510 public static void setAudioRouteForTesting(int audioRoute) {
511 audioRouteForTesting = Optional.of(audioRoute);
512 }
513
Eric Erfanianccca3152017-02-22 16:32:36 -0800514 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
515 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
516
517 // As soon as the user starts typing valid dialable keys on the
518 // keyboard (presumably to type DTMF tones) we start passing the
519 // key events to the DTMFDialer's onDialerKeyDown.
520 DialpadFragment dialpadFragment = getDialpadFragment();
521 if (dialpadFragment != null && dialpadFragment.isVisible()) {
522 return dialpadFragment.onDialerKeyDown(event);
523 }
524
525 return false;
526 }
527
528 public void dismissKeyguard(boolean dismiss) {
529 if (dismissKeyguard == dismiss) {
530 return;
531 }
532 dismissKeyguard = dismiss;
533 if (dismiss) {
534 inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
535 } else {
536 inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
537 }
538 }
539
540 public void showPostCharWaitDialog(String callId, String chars) {
541 if (inCallActivity.isVisible()) {
542 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
543 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
544
545 showPostCharWaitDialogOnResume = false;
546 showPostCharWaitDialogCallId = null;
547 showPostCharWaitDialogChars = null;
548 } else {
549 showPostCharWaitDialogOnResume = true;
550 showPostCharWaitDialogCallId = callId;
551 showPostCharWaitDialogChars = chars;
552 }
553 }
554
Eric Erfanian2ca43182017-08-31 06:57:16 -0700555 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800556 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700557 "InCallActivityCommon.maybeShowErrorDialogOnDisconnect",
558 "disconnect cause: %s",
559 disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800560
561 if (!inCallActivity.isFinishing()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700562 if (disconnectMessage.dialog != null) {
563 showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800564 }
565 }
566 }
567
568 /**
569 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
570 * be shown on launch.
571 *
572 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
573 * false} to indicate no change should be made to the dialpad visibility.
574 */
575 private void relaunchedFromDialer(boolean showDialpad) {
576 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
577 animateDialpadOnShow = true;
578
579 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
580 // If there's only one line in use, AND it's on hold, then we're sure the user
581 // wants to use the dialpad toward the exact line, so un-hold the holding line.
582 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
583 if (call != null && call.getState() == State.ONHOLD) {
584 call.unhold();
585 }
586 }
587 }
588
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700589 void dismissPendingDialogs() {
Eric Erfanianccca3152017-02-22 16:32:36 -0800590 if (dialog != null) {
591 dialog.dismiss();
592 dialog = null;
593 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700594 if (selectPhoneAccountDialogFragment != null) {
595 selectPhoneAccountDialogFragment.dismiss();
596 selectPhoneAccountDialogFragment = null;
597 }
Eric Erfanianc857f902017-05-15 14:05:33 -0700598
599 InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
600 (InternationalCallOnWifiDialogFragment)
601 inCallActivity
602 .getSupportFragmentManager()
603 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
604 if (internationalCallOnWifiFragment != null) {
605 LogUtil.i(
606 "InCallActivityCommon.dismissPendingDialogs",
607 "dismissing InternationalCallOnWifiDialogFragment");
608 internationalCallOnWifiFragment.dismiss();
609 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800610 }
611
Eric Erfanianccca3152017-02-22 16:32:36 -0800612 private void showErrorDialog(Dialog dialog, CharSequence message) {
613 LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
614 inCallActivity.dismissPendingDialogs();
615
616 // Show toast if apps is in background when dialog won't be visible.
617 if (!inCallActivity.isVisible()) {
618 Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
619 return;
620 }
621
622 this.dialog = dialog;
twyen8efb4952017-10-06 16:35:54 -0700623 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800624 dialog.setOnDismissListener(
625 new OnDismissListener() {
626 @Override
627 public void onDismiss(DialogInterface dialog) {
628 LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
twyen8efb4952017-10-06 16:35:54 -0700629 lock.release();
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 onDialogDismissed();
631 }
632 });
633 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
634 dialog.show();
635 }
636
637 private void onDialogDismissed() {
638 dialog = null;
639 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800640 }
641
642 public void enableInCallOrientationEventListener(boolean enable) {
643 if (enable) {
644 inCallOrientationEventListener.enable(true);
645 } else {
646 inCallOrientationEventListener.disable();
647 }
648 }
649
650 public void setExcludeFromRecents(boolean exclude) {
651 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
652 int taskId = inCallActivity.getTaskId();
653 for (int i = 0; i < tasks.size(); i++) {
654 ActivityManager.AppTask task = tasks.get(i);
655 try {
656 if (task.getTaskInfo().id == taskId) {
657 task.setExcludeFromRecents(exclude);
658 }
659 } catch (RuntimeException e) {
660 LogUtil.e(
661 "InCallActivityCommon.setExcludeFromRecents",
662 "RuntimeException when excluding task from recents.",
663 e);
664 }
665 }
666 }
667
Eric Erfanianc857f902017-05-15 14:05:33 -0700668 void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
669 LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
670 if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
671 LogUtil.i(
672 "InCallActivityCommon.showInternationalCallOnWifiDialog",
673 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
674 return;
675 }
676
677 InternationalCallOnWifiDialogFragment fragment =
678 InternationalCallOnWifiDialogFragment.newInstance(
679 call.getId(), internationalCallOnWifiCallback);
680 fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
681 }
682
Eric Erfanianccca3152017-02-22 16:32:36 -0800683 public void showWifiToLteHandoverToast(DialerCall call) {
684 if (call.hasShownWiFiToLteHandoverToast()) {
685 return;
686 }
687 Toast.makeText(
688 inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
689 .show();
690 call.setHasShownWiFiToLteHandoverToast();
691 }
692
693 public void showWifiFailedDialog(final DialerCall call) {
694 if (call.showWifiHandoverAlertAsToast()) {
695 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
696 Toast.makeText(
697 inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
698 .show();
699 return;
700 }
701
702 dismissPendingDialogs();
703
704 AlertDialog.Builder builder =
705 new AlertDialog.Builder(inCallActivity)
706 .setTitle(R.string.video_call_lte_to_wifi_failed_title);
707
708 // This allows us to use the theme of the dialog instead of the activity
709 View dialogCheckBoxView =
710 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
711 final CheckBox wifiHandoverFailureCheckbox =
712 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
713 wifiHandoverFailureCheckbox.setChecked(false);
714
twyen8efb4952017-10-06 16:35:54 -0700715 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800716 dialog =
717 builder
718 .setView(dialogCheckBoxView)
719 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
720 .setOnCancelListener(
721 new OnCancelListener() {
722 @Override
723 public void onCancel(DialogInterface dialog) {
724 onDialogDismissed();
725 }
726 })
727 .setPositiveButton(
728 android.R.string.ok,
729 new DialogInterface.OnClickListener() {
730 @Override
731 public void onClick(DialogInterface dialog, int id) {
732 call.setDoNotShowDialogForHandoffToWifiFailure(
733 wifiHandoverFailureCheckbox.isChecked());
734 dialog.cancel();
735 onDialogDismissed();
736 }
737 })
twyen8efb4952017-10-06 16:35:54 -0700738 .setOnDismissListener((dialog) -> lock.release())
Eric Erfanianccca3152017-02-22 16:32:36 -0800739 .create();
740
741 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
742 dialog.show();
743 }
744
Eric Erfanian938468d2017-10-24 14:05:52 -0700745 void updateNavigationBar(boolean isDialpadVisible) {
wangqifad3d872017-10-25 13:15:23 -0700746 if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) {
Eric Erfanian938468d2017-10-24 14:05:52 -0700747 View navigationBarBackground =
748 inCallActivity.getWindow().findViewById(R.id.navigation_bar_background);
749 if (navigationBarBackground != null) {
750 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
751 }
752 }
753 }
754
Eric Erfanianccca3152017-02-22 16:32:36 -0800755 public boolean showDialpadFragment(boolean show, boolean animate) {
756 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
757 boolean isDialpadVisible = isDialpadVisible();
758 LogUtil.i(
759 "InCallActivityCommon.showDialpadFragment",
760 "show: %b, animate: %b, " + "isDialpadVisible: %b",
761 show,
762 animate,
763 isDialpadVisible);
764 if (show == isDialpadVisible) {
765 return false;
766 }
767
768 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
769 if (dialpadFragmentManager == null) {
770 LogUtil.i(
771 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
772 return false;
773 }
774
775 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
776 // the listener is fired after an animation finishes.
777 if (!animate) {
778 if (show) {
779 performShowDialpadFragment(dialpadFragmentManager);
780 } else {
781 performHideDialpadFragment();
782 }
783 } else {
784 if (show) {
785 performShowDialpadFragment(dialpadFragmentManager);
786 getDialpadFragment().animateShowDialpad();
787 }
788 getDialpadFragment()
789 .getView()
790 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
791 }
792
793 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
794 if (sensor != null) {
795 sensor.onDialpadVisible(show);
796 }
797 showDialpadRequest = DIALPAD_REQUEST_NONE;
798 return true;
799 }
800
801 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
802 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
803 DialpadFragment dialpadFragment = getDialpadFragment();
804 if (dialpadFragment == null) {
805 transaction.add(
806 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
807 } else {
808 transaction.show(dialpadFragment);
809 }
810
811 transaction.commitAllowingStateLoss();
812 dialpadFragmentManager.executePendingTransactions();
813
814 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
Eric Erfanian938468d2017-10-24 14:05:52 -0700815 updateNavigationBar(true /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800816 }
817
818 private void performHideDialpadFragment() {
819 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
820 if (fragmentManager == null) {
821 LogUtil.e(
822 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
823 return;
824 }
825
826 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
827 if (fragment != null) {
828 FragmentTransaction transaction = fragmentManager.beginTransaction();
829 transaction.hide(fragment);
830 transaction.commitAllowingStateLoss();
831 fragmentManager.executePendingTransactions();
832 }
Eric Erfanian938468d2017-10-24 14:05:52 -0700833 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800834 }
835
836 public boolean isDialpadVisible() {
837 DialpadFragment dialpadFragment = getDialpadFragment();
838 return dialpadFragment != null && dialpadFragment.isVisible();
839 }
840
841 /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
842 @Nullable
843 private DialpadFragment getDialpadFragment() {
844 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
845 if (fragmentManager == null) {
846 return null;
847 }
848 return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
849 }
850
851 public void updateTaskDescription() {
852 Resources resources = inCallActivity.getResources();
853 int color;
854 if (resources.getBoolean(R.bool.is_layout_landscape)) {
855 color =
856 ResourcesCompat.getColor(
857 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
858 } else {
859 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
860 }
861
862 TaskDescription td =
863 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
864 inCallActivity.setTaskDescription(td);
865 }
866
867 public boolean hasPendingDialogs() {
868 return dialog != null;
869 }
870
871 private void internalResolveIntent(Intent intent) {
872 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
873 return;
874 }
875
876 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
877 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
878 // dialpad should be initially visible. If the extra isn't
879 // present at all, we just leave the dialpad in its previous state.
880 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
881 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
882
883 relaunchedFromDialer(showDialpad);
884 }
885
886 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
887 if (outgoingCall == null) {
888 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
889 }
890
Eric Erfanianccca3152017-02-22 16:32:36 -0800891 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800892 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
893
894 // InCallActivity is responsible for disconnecting a new outgoing call if there
895 // is no way of making it (i.e. no valid call capable accounts).
896 // If the version is not MSIM compatible, then ignore this code.
897 if (CompatUtils.isMSIMCompatible()
898 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
899 LogUtil.i(
900 "InCallActivityCommon.internalResolveIntent",
901 "call with no valid accounts, disconnecting");
902 outgoingCall.disconnect();
903 }
904
905 dismissKeyguard(true);
906 }
907
908 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700909 if (didShowAccountSelectionDialog) {
910 inCallActivity.hideMainInCallFragment();
911 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800912 }
913
914 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700915 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
916 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800917 return false;
918 }
919
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700920 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800921 List<PhoneAccountHandle> phoneAccountHandles;
922 if (extras != null) {
923 phoneAccountHandles =
924 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
925 } else {
926 phoneAccountHandles = new ArrayList<>();
927 }
928
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700929 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800930 SelectPhoneAccountDialogFragment.newInstance(
931 R.string.select_phone_account_for_calls,
932 true,
933 phoneAccountHandles,
934 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700935 waitingForAccountCall.getId());
936 selectPhoneAccountDialogFragment.show(
937 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800938 return true;
939 }
940}