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