blob: f92e785349b83de4aa07a4b2fef98c759180af7a [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;
linyuhf99f6302017-11-15 11:23:51 -0800108 private Dialog errorDialog;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700109 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -0800110 private Animation dialpadSlideInAnimation;
111 private Animation dialpadSlideOutAnimation;
112 private boolean animateDialpadOnShow;
113 private String dtmfTextToPreopulate;
114 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700115 // If activity is going to be recreated. This is usually happening in {@link onNewIntent}.
116 private boolean isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800117
Eric Erfanianc857f902017-05-15 14:05:33 -0700118 private final SelectPhoneAccountListener selectAccountListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800119 new SelectPhoneAccountListener() {
120 @Override
121 public void onPhoneAccountSelected(
122 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
123 DialerCall call = CallList.getInstance().getCallById(callId);
124 LogUtil.i(
125 "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
126 "call: " + call);
127 if (call != null) {
128 call.phoneAccountSelected(selectedAccountHandle, setDefault);
129 }
130 }
131
132 @Override
133 public void onDialogDismissed(String callId) {
134 DialerCall call = CallList.getInstance().getCallById(callId);
135 LogUtil.i(
136 "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
137 "disconnecting call: " + call);
138 if (call != null) {
139 call.disconnect();
140 }
141 }
142 };
143
Eric Erfanianc857f902017-05-15 14:05:33 -0700144 private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
145 new Callback() {
146 @Override
147 public void continueCall(@NonNull String callId) {
148 LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
149 }
150
151 @Override
152 public void cancelCall(@NonNull String callId) {
153 DialerCall call = CallList.getInstance().getCallById(callId);
154 if (call == null) {
155 LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
156 return;
157 }
158 LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
159 call.disconnect();
160 }
161 };
162
Eric Erfanianccca3152017-02-22 16:32:36 -0800163 public static void setIntentExtras(
164 Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
165 if (showDialpad) {
166 intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
167 }
168 intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
169 intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
170 }
171
172 public InCallActivityCommon(InCallActivity inCallActivity) {
173 this.inCallActivity = inCallActivity;
174 }
175
176 public void onCreate(Bundle icicle) {
linyuh9a915fc2017-11-09 16:27:13 -0800177 setWindowFlags();
Eric Erfanianccca3152017-02-22 16:32:36 -0800178
179 inCallActivity.setContentView(R.layout.incall_screen);
180
181 internalResolveIntent(inCallActivity.getIntent());
182
183 boolean isLandscape =
184 inCallActivity.getResources().getConfiguration().orientation
185 == Configuration.ORIENTATION_LANDSCAPE;
186 boolean isRtl = ViewUtil.isRtl();
187
188 if (isLandscape) {
189 dialpadSlideInAnimation =
190 AnimationUtils.loadAnimation(
191 inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
192 dialpadSlideOutAnimation =
193 AnimationUtils.loadAnimation(
194 inCallActivity,
195 isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
196 } else {
197 dialpadSlideInAnimation =
198 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
199 dialpadSlideOutAnimation =
200 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
201 }
202
203 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
204 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
205
206 dialpadSlideOutAnimation.setAnimationListener(
207 new AnimationListenerAdapter() {
208 @Override
209 public void onAnimationEnd(Animation animation) {
210 performHideDialpadFragment();
211 }
212 });
213
Eric Erfanian2ca43182017-08-31 06:57:16 -0700214 // Don't override the value if show dialpad request is true in intent extras.
215 if (icicle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800216 // If the dialpad was shown before, set variables indicating it should be shown and
217 // populated with the previous DTMF text. The dialpad is actually shown and populated
218 // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
219 if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
220 boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
221 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
222 animateDialpadOnShow = false;
223 }
224 dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
225
226 SelectPhoneAccountDialogFragment dialogFragment =
227 (SelectPhoneAccountDialogFragment)
228 inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
229 if (dialogFragment != null) {
230 dialogFragment.setListener(selectAccountListener);
231 }
232 }
233
Eric Erfanianc857f902017-05-15 14:05:33 -0700234 InternationalCallOnWifiDialogFragment existingInternationalFragment =
235 (InternationalCallOnWifiDialogFragment)
236 inCallActivity
237 .getSupportFragmentManager()
238 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
239 if (existingInternationalFragment != null) {
240 LogUtil.i(
241 "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
242 existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
243 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800244 }
245
246 public void onSaveInstanceState(Bundle out) {
247 // TODO: The dialpad fragment should handle this as part of its own state
linyuh69a25062017-11-15 16:18:51 -0800248 out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, inCallActivity.isDialpadVisible());
249 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800250 if (dialpadFragment != null) {
251 out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
252 }
253 }
254
255 public void onStart() {
wangqi9982f0d2017-10-11 17:46:07 -0700256 Trace.beginSection("InCallActivityCommon.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800257 // setting activity should be last thing in setup process
258 InCallPresenter.getInstance().setActivity(inCallActivity);
linyuh69a25062017-11-15 16:18:51 -0800259 inCallActivity.enableInCallOrientationEventListener(
Eric Erfanianccca3152017-02-22 16:32:36 -0800260 inCallActivity.getRequestedOrientation()
261 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
262
263 InCallPresenter.getInstance().onActivityStarted();
wangqi9982f0d2017-10-11 17:46:07 -0700264 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800265 }
266
267 public void onResume() {
wangqi9982f0d2017-10-11 17:46:07 -0700268 Trace.beginSection("InCallActivityCommon.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800269 if (InCallPresenter.getInstance().isReadyForTearDown()) {
270 LogUtil.i(
271 "InCallActivityCommon.onResume",
272 "InCallPresenter is ready for tear down, not sending updates");
273 } else {
274 updateTaskDescription();
roldenburg43073472017-11-15 12:31:06 -0800275 InCallPresenter.getInstance().onUiShowing(true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800276 }
277
278 // If there is a pending request to show or hide the dialpad, handle that now.
279 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
280 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
281 // Exit fullscreen so that the user has access to the dialpad hide/show button and
282 // can hide the dialpad. Important when showing the dialpad from within dialer.
283 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
284
285 inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
286 animateDialpadOnShow = false;
287
linyuh69a25062017-11-15 16:18:51 -0800288 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800289 if (dialpadFragment != null) {
290 dialpadFragment.setDtmfText(dtmfTextToPreopulate);
291 dtmfTextToPreopulate = null;
292 }
293 } else {
294 LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
linyuh69a25062017-11-15 16:18:51 -0800295 if (inCallActivity.getDialpadFragment() != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800296 inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
297 }
298 }
299 showDialpadRequest = DIALPAD_REQUEST_NONE;
300 }
linyuh69a25062017-11-15 16:18:51 -0800301 updateNavigationBar(inCallActivity.isDialpadVisible());
Eric Erfanianccca3152017-02-22 16:32:36 -0800302
303 if (showPostCharWaitDialogOnResume) {
304 showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
305 }
306
307 CallList.getInstance()
308 .onInCallUiShown(
309 inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
wangqi9982f0d2017-10-11 17:46:07 -0700310 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800311 }
312
313 // onPause is guaranteed to be called when the InCallActivity goes
314 // in the background.
315 public void onPause() {
linyuh69a25062017-11-15 16:18:51 -0800316 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800317 if (dialpadFragment != null) {
318 dialpadFragment.onDialerKeyUp(null);
319 }
roldenburg006ac372017-11-15 10:59:49 -0800320
roldenburg43073472017-11-15 12:31:06 -0800321 InCallPresenter.getInstance().onUiShowing(false);
roldenburg006ac372017-11-15 10:59:49 -0800322 if (inCallActivity.isFinishing()) {
323 InCallPresenter.getInstance().unsetActivity(inCallActivity);
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
linyuh69a25062017-11-15 16:18:51 -0800340 inCallActivity.enableInCallOrientationEventListener(false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800341 InCallPresenter.getInstance().updateIsChangingConfigurations();
342 InCallPresenter.getInstance().onActivityStopped();
yueg7b28abc2017-09-21 16:08:30 -0700343 if (!isRecreating) {
linyuhf99f6302017-11-15 11:23:51 -0800344 if (errorDialog != null) {
345 errorDialog.dismiss();
twyen8efb4952017-10-06 16:35:54 -0700346 }
yueg7b28abc2017-09-21 16:08:30 -0700347 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800348 }
349
350 public void onDestroy() {
351 InCallPresenter.getInstance().unsetActivity(inCallActivity);
352 InCallPresenter.getInstance().updateIsChangingConfigurations();
353 }
354
Eric Erfanian10b34a52017-05-04 08:23:17 -0700355 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800356 LogUtil.i("InCallActivityCommon.onNewIntent", "");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700357 this.isRecreating = isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800358
359 // We're being re-launched with a new Intent. Since it's possible for a
360 // single InCallActivity instance to persist indefinitely (even if we
361 // finish() ourselves), this sequence can potentially happen any time
362 // the InCallActivity needs to be displayed.
363
364 // Stash away the new intent so that we can get it in the future
365 // by calling getIntent(). (Otherwise getIntent() will return the
366 // original Intent from when we first got created!)
367 inCallActivity.setIntent(intent);
368
369 // Activities are always paused before receiving a new intent, so
370 // we can count on our onResume() method being called next.
371
372 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700373 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
374 if (!isRecreating) {
375 internalResolveIntent(intent);
376 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800377 }
378
379 public boolean onBackPressed(boolean isInCallScreenVisible) {
380 LogUtil.i("InCallActivityCommon.onBackPressed", "");
381
382 // BACK is also used to exit out of any "special modes" of the
383 // in-call UI:
384 if (!inCallActivity.isVisible()) {
385 return true;
386 }
387
388 if (!isInCallScreenVisible) {
389 return true;
390 }
391
linyuh69a25062017-11-15 16:18:51 -0800392 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800393 if (dialpadFragment != null && dialpadFragment.isVisible()) {
394 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
395 return true;
396 }
397
398 // Always disable the Back key while an incoming call is ringing
399 DialerCall call = CallList.getInstance().getIncomingCall();
400 if (call != null) {
401 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
402 return true;
403 }
404
405 // Nothing special to do. Fall back to the default behavior.
406 return false;
407 }
408
409 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh69a25062017-11-15 16:18:51 -0800410 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800411 // push input to the dialer.
412 if (dialpadFragment != null
413 && (dialpadFragment.isVisible())
414 && (dialpadFragment.onDialerKeyUp(event))) {
415 return true;
416 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
417 // Always consume CALL to be sure the PhoneWindow won't do anything with it
418 return true;
419 }
420 return false;
421 }
422
423 public boolean onKeyDown(int keyCode, KeyEvent event) {
424 switch (keyCode) {
425 case KeyEvent.KEYCODE_CALL:
426 boolean handled = InCallPresenter.getInstance().handleCallKey();
427 if (!handled) {
428 LogUtil.e(
429 "InCallActivityCommon.onKeyDown",
430 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
431 }
432 // Always consume CALL to be sure the PhoneWindow won't do anything with it
433 return true;
434
435 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
436 // The standard system-wide handling of the ENDCALL key
437 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
438 // already implements exactly what the UI spec wants,
439 // namely (1) "hang up" if there's a current active call,
440 // or (2) "don't answer" if there's a current ringing call.
441
442 case KeyEvent.KEYCODE_CAMERA:
443 // Disable the CAMERA button while in-call since it's too
444 // easy to press accidentally.
445 return true;
446
447 case KeyEvent.KEYCODE_VOLUME_UP:
448 case KeyEvent.KEYCODE_VOLUME_DOWN:
449 case KeyEvent.KEYCODE_VOLUME_MUTE:
450 // Ringer silencing handled by PhoneWindowManager.
451 break;
452
453 case KeyEvent.KEYCODE_MUTE:
454 TelecomAdapter.getInstance()
455 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
456 return true;
457
458 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
459 case KeyEvent.KEYCODE_SLASH:
460 if (LogUtil.isVerboseEnabled()) {
461 LogUtil.v(
462 "InCallActivityCommon.onKeyDown",
463 "----------- InCallActivity View dump --------------");
464 // Dump starting from the top-level view of the entire activity:
465 Window w = inCallActivity.getWindow();
466 View decorView = w.getDecorView();
467 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
468 return true;
469 }
470 break;
471 case KeyEvent.KEYCODE_EQUALS:
472 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700473 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800474 }
475
476 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
477 }
478
linyuh9a915fc2017-11-09 16:27:13 -0800479 private void setWindowFlags() {
480 // Allow the activity to be shown when the screen is locked and filter out touch events that are
481 // "too fat".
482 int flags =
483 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
484 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
485
486 // When the audio stream is not directed through Bluetooth, turn the screen on once the
487 // activity is shown.
488 final int audioRoute = getAudioRoute();
489 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH) {
490 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
491 }
492
493 inCallActivity.getWindow().addFlags(flags);
494 }
495
496 private static int getAudioRoute() {
497 if (audioRouteForTesting.isPresent()) {
498 return audioRouteForTesting.get();
499 }
500
501 return AudioModeProvider.getInstance().getAudioState().getRoute();
502 }
503
504 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
505 public static void setAudioRouteForTesting(int audioRoute) {
506 audioRouteForTesting = Optional.of(audioRoute);
507 }
508
Eric Erfanianccca3152017-02-22 16:32:36 -0800509 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
510 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
511
512 // As soon as the user starts typing valid dialable keys on the
513 // keyboard (presumably to type DTMF tones) we start passing the
514 // key events to the DTMFDialer's onDialerKeyDown.
linyuh69a25062017-11-15 16:18:51 -0800515 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800516 if (dialpadFragment != null && dialpadFragment.isVisible()) {
517 return dialpadFragment.onDialerKeyDown(event);
518 }
519
520 return false;
521 }
522
Eric Erfanianccca3152017-02-22 16:32:36 -0800523 public void showPostCharWaitDialog(String callId, String chars) {
524 if (inCallActivity.isVisible()) {
525 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
526 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
527
528 showPostCharWaitDialogOnResume = false;
529 showPostCharWaitDialogCallId = null;
530 showPostCharWaitDialogChars = null;
531 } else {
532 showPostCharWaitDialogOnResume = true;
533 showPostCharWaitDialogCallId = callId;
534 showPostCharWaitDialogChars = chars;
535 }
536 }
537
Eric Erfanian2ca43182017-08-31 06:57:16 -0700538 public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800539 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -0700540 "InCallActivityCommon.maybeShowErrorDialogOnDisconnect",
541 "disconnect cause: %s",
542 disconnectMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800543
544 if (!inCallActivity.isFinishing()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700545 if (disconnectMessage.dialog != null) {
546 showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage);
Eric Erfanianccca3152017-02-22 16:32:36 -0800547 }
548 }
549 }
550
551 /**
552 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
553 * be shown on launch.
554 *
555 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
556 * false} to indicate no change should be made to the dialpad visibility.
557 */
558 private void relaunchedFromDialer(boolean showDialpad) {
559 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
560 animateDialpadOnShow = true;
561
562 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
563 // If there's only one line in use, AND it's on hold, then we're sure the user
564 // wants to use the dialpad toward the exact line, so un-hold the holding line.
565 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
566 if (call != null && call.getState() == State.ONHOLD) {
567 call.unhold();
568 }
569 }
570 }
571
Eric Erfanianccca3152017-02-22 16:32:36 -0800572 private void showErrorDialog(Dialog dialog, CharSequence message) {
573 LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
574 inCallActivity.dismissPendingDialogs();
575
576 // Show toast if apps is in background when dialog won't be visible.
577 if (!inCallActivity.isVisible()) {
578 Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
579 return;
580 }
581
linyuhf99f6302017-11-15 11:23:51 -0800582 this.errorDialog = dialog;
twyen8efb4952017-10-06 16:35:54 -0700583 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
Eric Erfanianccca3152017-02-22 16:32:36 -0800584 dialog.setOnDismissListener(
585 new OnDismissListener() {
586 @Override
587 public void onDismiss(DialogInterface dialog) {
588 LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
twyen8efb4952017-10-06 16:35:54 -0700589 lock.release();
Eric Erfanianccca3152017-02-22 16:32:36 -0800590 onDialogDismissed();
591 }
592 });
593 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
594 dialog.show();
595 }
596
597 private void onDialogDismissed() {
linyuhf99f6302017-11-15 11:23:51 -0800598 errorDialog = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800599 CallList.getInstance().onErrorDialogDismissed();
Eric Erfanianccca3152017-02-22 16:32:36 -0800600 }
601
Eric Erfanianccca3152017-02-22 16:32:36 -0800602 public void setExcludeFromRecents(boolean exclude) {
603 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
604 int taskId = inCallActivity.getTaskId();
605 for (int i = 0; i < tasks.size(); i++) {
606 ActivityManager.AppTask task = tasks.get(i);
607 try {
608 if (task.getTaskInfo().id == taskId) {
609 task.setExcludeFromRecents(exclude);
610 }
611 } catch (RuntimeException e) {
612 LogUtil.e(
613 "InCallActivityCommon.setExcludeFromRecents",
614 "RuntimeException when excluding task from recents.",
615 e);
616 }
617 }
618 }
619
Eric Erfanianc857f902017-05-15 14:05:33 -0700620 void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
621 LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
622 if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
623 LogUtil.i(
624 "InCallActivityCommon.showInternationalCallOnWifiDialog",
625 "InternationalCallOnWifiDialogFragment.shouldShow returned false");
626 return;
627 }
628
629 InternationalCallOnWifiDialogFragment fragment =
630 InternationalCallOnWifiDialogFragment.newInstance(
631 call.getId(), internationalCallOnWifiCallback);
632 fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
633 }
634
Eric Erfanianccca3152017-02-22 16:32:36 -0800635 public void showWifiToLteHandoverToast(DialerCall call) {
636 if (call.hasShownWiFiToLteHandoverToast()) {
637 return;
638 }
639 Toast.makeText(
640 inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
641 .show();
642 call.setHasShownWiFiToLteHandoverToast();
643 }
644
645 public void showWifiFailedDialog(final DialerCall call) {
646 if (call.showWifiHandoverAlertAsToast()) {
647 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
648 Toast.makeText(
649 inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
650 .show();
651 return;
652 }
653
linyuhf99f6302017-11-15 11:23:51 -0800654 inCallActivity.dismissPendingDialogs();
Eric Erfanianccca3152017-02-22 16:32:36 -0800655
656 AlertDialog.Builder builder =
657 new AlertDialog.Builder(inCallActivity)
658 .setTitle(R.string.video_call_lte_to_wifi_failed_title);
659
660 // This allows us to use the theme of the dialog instead of the activity
661 View dialogCheckBoxView =
662 View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
663 final CheckBox wifiHandoverFailureCheckbox =
664 (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
665 wifiHandoverFailureCheckbox.setChecked(false);
666
twyen8efb4952017-10-06 16:35:54 -0700667 InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
linyuhf99f6302017-11-15 11:23:51 -0800668 errorDialog =
Eric Erfanianccca3152017-02-22 16:32:36 -0800669 builder
670 .setView(dialogCheckBoxView)
671 .setMessage(R.string.video_call_lte_to_wifi_failed_message)
672 .setOnCancelListener(
673 new OnCancelListener() {
674 @Override
675 public void onCancel(DialogInterface dialog) {
676 onDialogDismissed();
677 }
678 })
679 .setPositiveButton(
680 android.R.string.ok,
681 new DialogInterface.OnClickListener() {
682 @Override
683 public void onClick(DialogInterface dialog, int id) {
684 call.setDoNotShowDialogForHandoffToWifiFailure(
685 wifiHandoverFailureCheckbox.isChecked());
686 dialog.cancel();
687 onDialogDismissed();
688 }
689 })
twyen8efb4952017-10-06 16:35:54 -0700690 .setOnDismissListener((dialog) -> lock.release())
Eric Erfanianccca3152017-02-22 16:32:36 -0800691 .create();
692
693 LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
linyuhf99f6302017-11-15 11:23:51 -0800694 errorDialog.show();
Eric Erfanianccca3152017-02-22 16:32:36 -0800695 }
696
Eric Erfanian938468d2017-10-24 14:05:52 -0700697 void updateNavigationBar(boolean isDialpadVisible) {
wangqifad3d872017-10-25 13:15:23 -0700698 if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) {
Eric Erfanian938468d2017-10-24 14:05:52 -0700699 View navigationBarBackground =
700 inCallActivity.getWindow().findViewById(R.id.navigation_bar_background);
701 if (navigationBarBackground != null) {
702 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
703 }
704 }
705 }
706
Eric Erfanianccca3152017-02-22 16:32:36 -0800707 public boolean showDialpadFragment(boolean show, boolean animate) {
708 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
linyuh69a25062017-11-15 16:18:51 -0800709 boolean isDialpadVisible = inCallActivity.isDialpadVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -0800710 LogUtil.i(
711 "InCallActivityCommon.showDialpadFragment",
712 "show: %b, animate: %b, " + "isDialpadVisible: %b",
713 show,
714 animate,
715 isDialpadVisible);
716 if (show == isDialpadVisible) {
717 return false;
718 }
719
720 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
721 if (dialpadFragmentManager == null) {
722 LogUtil.i(
723 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
724 return false;
725 }
726
727 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
728 // the listener is fired after an animation finishes.
729 if (!animate) {
730 if (show) {
731 performShowDialpadFragment(dialpadFragmentManager);
732 } else {
733 performHideDialpadFragment();
734 }
735 } else {
736 if (show) {
737 performShowDialpadFragment(dialpadFragmentManager);
linyuh69a25062017-11-15 16:18:51 -0800738 inCallActivity.getDialpadFragment().animateShowDialpad();
Eric Erfanianccca3152017-02-22 16:32:36 -0800739 }
linyuh69a25062017-11-15 16:18:51 -0800740 inCallActivity
741 .getDialpadFragment()
Eric Erfanianccca3152017-02-22 16:32:36 -0800742 .getView()
743 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
744 }
745
746 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
747 if (sensor != null) {
748 sensor.onDialpadVisible(show);
749 }
750 showDialpadRequest = DIALPAD_REQUEST_NONE;
751 return true;
752 }
753
754 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
755 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
linyuh69a25062017-11-15 16:18:51 -0800756 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800757 if (dialpadFragment == null) {
758 transaction.add(
759 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
760 } else {
761 transaction.show(dialpadFragment);
762 }
763
764 transaction.commitAllowingStateLoss();
765 dialpadFragmentManager.executePendingTransactions();
766
767 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
Eric Erfanian938468d2017-10-24 14:05:52 -0700768 updateNavigationBar(true /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800769 }
770
771 private void performHideDialpadFragment() {
772 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
773 if (fragmentManager == null) {
774 LogUtil.e(
775 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
776 return;
777 }
778
779 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
780 if (fragment != null) {
781 FragmentTransaction transaction = fragmentManager.beginTransaction();
782 transaction.hide(fragment);
783 transaction.commitAllowingStateLoss();
784 fragmentManager.executePendingTransactions();
785 }
Eric Erfanian938468d2017-10-24 14:05:52 -0700786 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800787 }
788
Eric Erfanianccca3152017-02-22 16:32:36 -0800789 public void updateTaskDescription() {
790 Resources resources = inCallActivity.getResources();
791 int color;
792 if (resources.getBoolean(R.bool.is_layout_landscape)) {
793 color =
794 ResourcesCompat.getColor(
795 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
796 } else {
797 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
798 }
799
800 TaskDescription td =
801 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
802 inCallActivity.setTaskDescription(td);
803 }
804
Eric Erfanianccca3152017-02-22 16:32:36 -0800805 private void internalResolveIntent(Intent intent) {
806 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
807 return;
808 }
809
810 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
811 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
812 // dialpad should be initially visible. If the extra isn't
813 // present at all, we just leave the dialpad in its previous state.
814 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
815 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
816
817 relaunchedFromDialer(showDialpad);
818 }
819
820 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
821 if (outgoingCall == null) {
822 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
823 }
824
Eric Erfanianccca3152017-02-22 16:32:36 -0800825 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800826 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
827
828 // InCallActivity is responsible for disconnecting a new outgoing call if there
829 // is no way of making it (i.e. no valid call capable accounts).
830 // If the version is not MSIM compatible, then ignore this code.
831 if (CompatUtils.isMSIMCompatible()
832 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
833 LogUtil.i(
834 "InCallActivityCommon.internalResolveIntent",
835 "call with no valid accounts, disconnecting");
836 outgoingCall.disconnect();
837 }
838
linyuh9c327da2017-11-14 12:33:48 -0800839 inCallActivity.dismissKeyguard(true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800840 }
841
842 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700843 if (didShowAccountSelectionDialog) {
844 inCallActivity.hideMainInCallFragment();
845 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800846 }
847
848 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700849 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
850 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800851 return false;
852 }
853
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700854 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 List<PhoneAccountHandle> phoneAccountHandles;
856 if (extras != null) {
857 phoneAccountHandles =
858 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
859 } else {
860 phoneAccountHandles = new ArrayList<>();
861 }
862
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700863 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800864 SelectPhoneAccountDialogFragment.newInstance(
865 R.string.select_phone_account_for_calls,
866 true,
867 phoneAccountHandles,
868 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700869 waitingForAccountCall.getId());
870 selectPhoneAccountDialogFragment.show(
871 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800872 return true;
873 }
linyuhf99f6302017-11-15 11:23:51 -0800874
875 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
876 @Deprecated
877 @Nullable
878 Dialog getErrorDialog() {
879 return errorDialog;
880 }
881
882 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
883 @Deprecated
884 void setErrorDialog(@Nullable Dialog errorDialog) {
885 this.errorDialog = errorDialog;
886 }
887
888 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
889 @Deprecated
890 @Nullable
891 SelectPhoneAccountDialogFragment getSelectPhoneAccountDialogFragment() {
892 return selectPhoneAccountDialogFragment;
893 }
894
895 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
896 @Deprecated
897 void setSelectPhoneAccountDialogFragment(
898 @Nullable SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment) {
899 this.selectPhoneAccountDialogFragment = selectPhoneAccountDialogFragment;
900 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800901}