blob: 8f82295ed251bd269652ee68eb09fe04bfce441f [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;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.app.Dialog;
Eric Erfanian2ca43182017-08-31 06:57:16 -070023import android.app.KeyguardManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080024import android.content.Intent;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.os.Bundle;
wangqi9982f0d2017-10-11 17:46:07 -070028import android.os.Trace;
Eric Erfanianccca3152017-02-22 16:32:36 -080029import android.support.annotation.IntDef;
30import android.support.annotation.NonNull;
31import android.support.annotation.Nullable;
linyuh9a915fc2017-11-09 16:27:13 -080032import android.support.annotation.VisibleForTesting;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.support.v4.app.Fragment;
34import android.support.v4.app.FragmentManager;
35import android.support.v4.app.FragmentTransaction;
36import android.support.v4.content.res.ResourcesCompat;
linyuh9a915fc2017-11-09 16:27:13 -080037import android.telecom.CallAudioState;
Eric Erfanianccca3152017-02-22 16:32:36 -080038import android.telecom.PhoneAccountHandle;
Eric Erfanianccca3152017-02-22 16:32:36 -080039import android.view.KeyEvent;
40import android.view.View;
41import android.view.Window;
42import android.view.WindowManager;
43import android.view.animation.Animation;
44import android.view.animation.AnimationUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080045import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
46import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
47import com.android.dialer.animation.AnimUtils;
48import com.android.dialer.animation.AnimationListenerAdapter;
49import com.android.dialer.common.LogUtil;
wangqifad3d872017-10-25 13:15:23 -070050import com.android.dialer.compat.ActivityCompat;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import com.android.dialer.compat.CompatUtils;
52import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -070053import com.android.dialer.logging.ScreenEvent;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import com.android.dialer.util.ViewUtil;
Eric Erfanian8369df02017-05-03 10:27:13 -070055import com.android.incallui.audiomode.AudioModeProvider;
Eric Erfanianccca3152017-02-22 16:32:36 -080056import com.android.incallui.call.CallList;
57import com.android.incallui.call.DialerCall;
58import com.android.incallui.call.DialerCall.State;
59import com.android.incallui.call.TelecomAdapter;
Eric Erfanianc857f902017-05-15 14:05:33 -070060import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
61import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
linyuh9a915fc2017-11-09 16:27:13 -080062import com.google.common.base.Optional;
Eric Erfanianccca3152017-02-22 16:32:36 -080063import java.lang.annotation.Retention;
64import java.lang.annotation.RetentionPolicy;
65import java.util.ArrayList;
66import java.util.List;
67
68/** Shared functionality between the new and old in call activity. */
69public class InCallActivityCommon {
70
71 private static final String INTENT_EXTRA_SHOW_DIALPAD = "InCallActivity.show_dialpad";
72 private static final String INTENT_EXTRA_NEW_OUTGOING_CALL = "InCallActivity.new_outgoing_call";
73 private static final String INTENT_EXTRA_FOR_FULL_SCREEN =
74 "InCallActivity.for_full_screen_intent";
75
76 private static final String DIALPAD_TEXT_KEY = "InCallActivity.dialpad_text";
77
78 private static final String TAG_SELECT_ACCOUNT_FRAGMENT = "tag_select_account_fragment";
79 private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
Eric Erfanianc857f902017-05-15 14:05:33 -070080 private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
Eric Erfanianccca3152017-02-22 16:32:36 -080081
82 @Retention(RetentionPolicy.SOURCE)
83 @IntDef({
84 DIALPAD_REQUEST_NONE,
85 DIALPAD_REQUEST_SHOW,
86 DIALPAD_REQUEST_HIDE,
87 })
88 @interface DialpadRequestType {}
89
90 private static final int DIALPAD_REQUEST_NONE = 1;
91 private static final int DIALPAD_REQUEST_SHOW = 2;
92 private static final int DIALPAD_REQUEST_HIDE = 3;
93
linyuh9a915fc2017-11-09 16:27:13 -080094 private static Optional<Integer> audioRouteForTesting = Optional.absent();
95
Eric Erfanianccca3152017-02-22 16:32:36 -080096 private final InCallActivity inCallActivity;
Eric Erfanianccca3152017-02-22 16:32:36 -080097 private boolean showPostCharWaitDialogOnResume;
98 private String showPostCharWaitDialogCallId;
99 private String showPostCharWaitDialogChars;
linyuhf99f6302017-11-15 11:23:51 -0800100 private Dialog errorDialog;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700101 private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -0800102 private Animation dialpadSlideInAnimation;
103 private Animation dialpadSlideOutAnimation;
104 private boolean animateDialpadOnShow;
105 private String dtmfTextToPreopulate;
106 @DialpadRequestType private int showDialpadRequest = DIALPAD_REQUEST_NONE;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700107 // If activity is going to be recreated. This is usually happening in {@link onNewIntent}.
108 private boolean isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800109
Eric Erfanianc857f902017-05-15 14:05:33 -0700110 private final SelectPhoneAccountListener selectAccountListener =
Eric Erfanianccca3152017-02-22 16:32:36 -0800111 new SelectPhoneAccountListener() {
112 @Override
113 public void onPhoneAccountSelected(
114 PhoneAccountHandle selectedAccountHandle, boolean setDefault, String callId) {
115 DialerCall call = CallList.getInstance().getCallById(callId);
116 LogUtil.i(
117 "InCallActivityCommon.SelectPhoneAccountListener.onPhoneAccountSelected",
118 "call: " + call);
119 if (call != null) {
120 call.phoneAccountSelected(selectedAccountHandle, setDefault);
121 }
122 }
123
124 @Override
125 public void onDialogDismissed(String callId) {
126 DialerCall call = CallList.getInstance().getCallById(callId);
127 LogUtil.i(
128 "InCallActivityCommon.SelectPhoneAccountListener.onDialogDismissed",
129 "disconnecting call: " + call);
130 if (call != null) {
131 call.disconnect();
132 }
133 }
134 };
135
Eric Erfanianc857f902017-05-15 14:05:33 -0700136 private InternationalCallOnWifiDialogFragment.Callback internationalCallOnWifiCallback =
137 new Callback() {
138 @Override
139 public void continueCall(@NonNull String callId) {
140 LogUtil.i("InCallActivityCommon.continueCall", "continuing call with id: %s", callId);
141 }
142
143 @Override
144 public void cancelCall(@NonNull String callId) {
145 DialerCall call = CallList.getInstance().getCallById(callId);
146 if (call == null) {
147 LogUtil.i("InCallActivityCommon.cancelCall", "call destroyed before dialog closed");
148 return;
149 }
150 LogUtil.i("InCallActivityCommon.cancelCall", "disconnecting international call on wifi");
151 call.disconnect();
152 }
153 };
154
Eric Erfanianccca3152017-02-22 16:32:36 -0800155 public static void setIntentExtras(
156 Intent intent, boolean showDialpad, boolean newOutgoingCall, boolean isForFullScreen) {
157 if (showDialpad) {
158 intent.putExtra(INTENT_EXTRA_SHOW_DIALPAD, true);
159 }
160 intent.putExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, newOutgoingCall);
161 intent.putExtra(INTENT_EXTRA_FOR_FULL_SCREEN, isForFullScreen);
162 }
163
164 public InCallActivityCommon(InCallActivity inCallActivity) {
165 this.inCallActivity = inCallActivity;
166 }
167
168 public void onCreate(Bundle icicle) {
linyuh9a915fc2017-11-09 16:27:13 -0800169 setWindowFlags();
Eric Erfanianccca3152017-02-22 16:32:36 -0800170
171 inCallActivity.setContentView(R.layout.incall_screen);
172
173 internalResolveIntent(inCallActivity.getIntent());
174
175 boolean isLandscape =
176 inCallActivity.getResources().getConfiguration().orientation
177 == Configuration.ORIENTATION_LANDSCAPE;
178 boolean isRtl = ViewUtil.isRtl();
179
180 if (isLandscape) {
181 dialpadSlideInAnimation =
182 AnimationUtils.loadAnimation(
183 inCallActivity, isRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
184 dialpadSlideOutAnimation =
185 AnimationUtils.loadAnimation(
186 inCallActivity,
187 isRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
188 } else {
189 dialpadSlideInAnimation =
190 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_in_bottom);
191 dialpadSlideOutAnimation =
192 AnimationUtils.loadAnimation(inCallActivity, R.anim.dialpad_slide_out_bottom);
193 }
194
195 dialpadSlideInAnimation.setInterpolator(AnimUtils.EASE_IN);
196 dialpadSlideOutAnimation.setInterpolator(AnimUtils.EASE_OUT);
197
198 dialpadSlideOutAnimation.setAnimationListener(
199 new AnimationListenerAdapter() {
200 @Override
201 public void onAnimationEnd(Animation animation) {
202 performHideDialpadFragment();
203 }
204 });
205
Eric Erfanian2ca43182017-08-31 06:57:16 -0700206 // Don't override the value if show dialpad request is true in intent extras.
207 if (icicle != null && showDialpadRequest == DIALPAD_REQUEST_NONE) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800208 // If the dialpad was shown before, set variables indicating it should be shown and
209 // populated with the previous DTMF text. The dialpad is actually shown and populated
210 // in onResume() to ensure the hosting fragment has been inflated and is ready to receive it.
211 if (icicle.containsKey(INTENT_EXTRA_SHOW_DIALPAD)) {
212 boolean showDialpad = icicle.getBoolean(INTENT_EXTRA_SHOW_DIALPAD);
213 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
214 animateDialpadOnShow = false;
215 }
216 dtmfTextToPreopulate = icicle.getString(DIALPAD_TEXT_KEY);
217
218 SelectPhoneAccountDialogFragment dialogFragment =
219 (SelectPhoneAccountDialogFragment)
220 inCallActivity.getFragmentManager().findFragmentByTag(TAG_SELECT_ACCOUNT_FRAGMENT);
221 if (dialogFragment != null) {
222 dialogFragment.setListener(selectAccountListener);
223 }
224 }
225
Eric Erfanianc857f902017-05-15 14:05:33 -0700226 InternationalCallOnWifiDialogFragment existingInternationalFragment =
227 (InternationalCallOnWifiDialogFragment)
228 inCallActivity
229 .getSupportFragmentManager()
230 .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
231 if (existingInternationalFragment != null) {
232 LogUtil.i(
233 "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
234 existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
235 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 }
237
238 public void onSaveInstanceState(Bundle out) {
239 // TODO: The dialpad fragment should handle this as part of its own state
linyuh69a25062017-11-15 16:18:51 -0800240 out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, inCallActivity.isDialpadVisible());
241 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800242 if (dialpadFragment != null) {
243 out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
244 }
245 }
246
247 public void onStart() {
wangqi9982f0d2017-10-11 17:46:07 -0700248 Trace.beginSection("InCallActivityCommon.onStart");
Eric Erfanianccca3152017-02-22 16:32:36 -0800249 // setting activity should be last thing in setup process
250 InCallPresenter.getInstance().setActivity(inCallActivity);
linyuh69a25062017-11-15 16:18:51 -0800251 inCallActivity.enableInCallOrientationEventListener(
Eric Erfanianccca3152017-02-22 16:32:36 -0800252 inCallActivity.getRequestedOrientation()
253 == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
254
255 InCallPresenter.getInstance().onActivityStarted();
wangqi9982f0d2017-10-11 17:46:07 -0700256 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800257 }
258
259 public void onResume() {
wangqi9982f0d2017-10-11 17:46:07 -0700260 Trace.beginSection("InCallActivityCommon.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800261 if (InCallPresenter.getInstance().isReadyForTearDown()) {
262 LogUtil.i(
263 "InCallActivityCommon.onResume",
264 "InCallPresenter is ready for tear down, not sending updates");
265 } else {
266 updateTaskDescription();
roldenburg43073472017-11-15 12:31:06 -0800267 InCallPresenter.getInstance().onUiShowing(true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800268 }
269
270 // If there is a pending request to show or hide the dialpad, handle that now.
271 if (showDialpadRequest != DIALPAD_REQUEST_NONE) {
272 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
273 // Exit fullscreen so that the user has access to the dialpad hide/show button and
274 // can hide the dialpad. Important when showing the dialpad from within dialer.
275 InCallPresenter.getInstance().setFullScreen(false, true /* force */);
276
277 inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
278 animateDialpadOnShow = false;
279
linyuh69a25062017-11-15 16:18:51 -0800280 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800281 if (dialpadFragment != null) {
282 dialpadFragment.setDtmfText(dtmfTextToPreopulate);
283 dtmfTextToPreopulate = null;
284 }
285 } else {
286 LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
linyuh69a25062017-11-15 16:18:51 -0800287 if (inCallActivity.getDialpadFragment() != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800288 inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
289 }
290 }
291 showDialpadRequest = DIALPAD_REQUEST_NONE;
292 }
linyuh69a25062017-11-15 16:18:51 -0800293 updateNavigationBar(inCallActivity.isDialpadVisible());
Eric Erfanianccca3152017-02-22 16:32:36 -0800294
295 if (showPostCharWaitDialogOnResume) {
296 showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
297 }
298
299 CallList.getInstance()
300 .onInCallUiShown(
301 inCallActivity.getIntent().getBooleanExtra(INTENT_EXTRA_FOR_FULL_SCREEN, false));
wangqi9982f0d2017-10-11 17:46:07 -0700302 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800303 }
304
305 // onPause is guaranteed to be called when the InCallActivity goes
306 // in the background.
307 public void onPause() {
linyuh69a25062017-11-15 16:18:51 -0800308 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800309 if (dialpadFragment != null) {
310 dialpadFragment.onDialerKeyUp(null);
311 }
roldenburg006ac372017-11-15 10:59:49 -0800312
roldenburg43073472017-11-15 12:31:06 -0800313 InCallPresenter.getInstance().onUiShowing(false);
roldenburg006ac372017-11-15 10:59:49 -0800314 if (inCallActivity.isFinishing()) {
315 InCallPresenter.getInstance().unsetActivity(inCallActivity);
316 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800317 }
318
319 public void onStop() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700320 // Disconnects call waiting for account when activity is hidden e.g. user press home button.
321 // This is necessary otherwise the pending call will stuck on account choose and no new call
Eric Erfanian938468d2017-10-24 14:05:52 -0700322 // will be able to create. See a bug for more details.
Eric Erfanian2ca43182017-08-31 06:57:16 -0700323 // Skip this on locked screen since the activity may go over life cycle and start again.
324 if (!isRecreating
325 && !inCallActivity.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
326 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
327 if (waitingForAccountCall != null) {
328 waitingForAccountCall.disconnect();
329 }
330 }
331
linyuh69a25062017-11-15 16:18:51 -0800332 inCallActivity.enableInCallOrientationEventListener(false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800333 InCallPresenter.getInstance().updateIsChangingConfigurations();
334 InCallPresenter.getInstance().onActivityStopped();
yueg7b28abc2017-09-21 16:08:30 -0700335 if (!isRecreating) {
linyuhf99f6302017-11-15 11:23:51 -0800336 if (errorDialog != null) {
337 errorDialog.dismiss();
twyen8efb4952017-10-06 16:35:54 -0700338 }
yueg7b28abc2017-09-21 16:08:30 -0700339 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800340 }
341
342 public void onDestroy() {
343 InCallPresenter.getInstance().unsetActivity(inCallActivity);
344 InCallPresenter.getInstance().updateIsChangingConfigurations();
345 }
346
Eric Erfanian10b34a52017-05-04 08:23:17 -0700347 void onNewIntent(Intent intent, boolean isRecreating) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800348 LogUtil.i("InCallActivityCommon.onNewIntent", "");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700349 this.isRecreating = isRecreating;
Eric Erfanianccca3152017-02-22 16:32:36 -0800350
351 // We're being re-launched with a new Intent. Since it's possible for a
352 // single InCallActivity instance to persist indefinitely (even if we
353 // finish() ourselves), this sequence can potentially happen any time
354 // the InCallActivity needs to be displayed.
355
356 // Stash away the new intent so that we can get it in the future
357 // by calling getIntent(). (Otherwise getIntent() will return the
358 // original Intent from when we first got created!)
359 inCallActivity.setIntent(intent);
360
361 // Activities are always paused before receiving a new intent, so
362 // we can count on our onResume() method being called next.
363
364 // Just like in onCreate(), handle the intent.
Eric Erfanian10b34a52017-05-04 08:23:17 -0700365 // Skip if InCallActivity is going to recreate since this will be called in onCreate().
366 if (!isRecreating) {
367 internalResolveIntent(intent);
368 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800369 }
370
371 public boolean onBackPressed(boolean isInCallScreenVisible) {
372 LogUtil.i("InCallActivityCommon.onBackPressed", "");
373
374 // BACK is also used to exit out of any "special modes" of the
375 // in-call UI:
376 if (!inCallActivity.isVisible()) {
377 return true;
378 }
379
380 if (!isInCallScreenVisible) {
381 return true;
382 }
383
linyuh69a25062017-11-15 16:18:51 -0800384 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800385 if (dialpadFragment != null && dialpadFragment.isVisible()) {
386 inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
387 return true;
388 }
389
390 // Always disable the Back key while an incoming call is ringing
391 DialerCall call = CallList.getInstance().getIncomingCall();
392 if (call != null) {
393 LogUtil.i("InCallActivityCommon.onBackPressed", "consume Back press for an incoming call");
394 return true;
395 }
396
397 // Nothing special to do. Fall back to the default behavior.
398 return false;
399 }
400
401 public boolean onKeyUp(int keyCode, KeyEvent event) {
linyuh69a25062017-11-15 16:18:51 -0800402 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800403 // push input to the dialer.
404 if (dialpadFragment != null
405 && (dialpadFragment.isVisible())
406 && (dialpadFragment.onDialerKeyUp(event))) {
407 return true;
408 } else if (keyCode == KeyEvent.KEYCODE_CALL) {
409 // Always consume CALL to be sure the PhoneWindow won't do anything with it
410 return true;
411 }
412 return false;
413 }
414
415 public boolean onKeyDown(int keyCode, KeyEvent event) {
416 switch (keyCode) {
417 case KeyEvent.KEYCODE_CALL:
418 boolean handled = InCallPresenter.getInstance().handleCallKey();
419 if (!handled) {
420 LogUtil.e(
421 "InCallActivityCommon.onKeyDown",
422 "InCallPresenter should always handle KEYCODE_CALL in onKeyDown");
423 }
424 // Always consume CALL to be sure the PhoneWindow won't do anything with it
425 return true;
426
427 // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
428 // The standard system-wide handling of the ENDCALL key
429 // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
430 // already implements exactly what the UI spec wants,
431 // namely (1) "hang up" if there's a current active call,
432 // or (2) "don't answer" if there's a current ringing call.
433
434 case KeyEvent.KEYCODE_CAMERA:
435 // Disable the CAMERA button while in-call since it's too
436 // easy to press accidentally.
437 return true;
438
439 case KeyEvent.KEYCODE_VOLUME_UP:
440 case KeyEvent.KEYCODE_VOLUME_DOWN:
441 case KeyEvent.KEYCODE_VOLUME_MUTE:
442 // Ringer silencing handled by PhoneWindowManager.
443 break;
444
445 case KeyEvent.KEYCODE_MUTE:
446 TelecomAdapter.getInstance()
447 .mute(!AudioModeProvider.getInstance().getAudioState().isMuted());
448 return true;
449
450 // Various testing/debugging features, enabled ONLY when VERBOSE == true.
451 case KeyEvent.KEYCODE_SLASH:
452 if (LogUtil.isVerboseEnabled()) {
453 LogUtil.v(
454 "InCallActivityCommon.onKeyDown",
455 "----------- InCallActivity View dump --------------");
456 // Dump starting from the top-level view of the entire activity:
457 Window w = inCallActivity.getWindow();
458 View decorView = w.getDecorView();
459 LogUtil.v("InCallActivityCommon.onKeyDown", "View dump:" + decorView);
460 return true;
461 }
462 break;
463 case KeyEvent.KEYCODE_EQUALS:
464 break;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700465 default: // fall out
Eric Erfanianccca3152017-02-22 16:32:36 -0800466 }
467
468 return event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event);
469 }
470
linyuh9a915fc2017-11-09 16:27:13 -0800471 private void setWindowFlags() {
472 // Allow the activity to be shown when the screen is locked and filter out touch events that are
473 // "too fat".
474 int flags =
475 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
476 | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
477
478 // When the audio stream is not directed through Bluetooth, turn the screen on once the
479 // activity is shown.
480 final int audioRoute = getAudioRoute();
481 if (audioRoute != CallAudioState.ROUTE_BLUETOOTH) {
482 flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
483 }
484
485 inCallActivity.getWindow().addFlags(flags);
486 }
487
488 private static int getAudioRoute() {
489 if (audioRouteForTesting.isPresent()) {
490 return audioRouteForTesting.get();
491 }
492
493 return AudioModeProvider.getInstance().getAudioState().getRoute();
494 }
495
496 @VisibleForTesting(otherwise = VisibleForTesting.NONE)
497 public static void setAudioRouteForTesting(int audioRoute) {
498 audioRouteForTesting = Optional.of(audioRoute);
499 }
500
Eric Erfanianccca3152017-02-22 16:32:36 -0800501 private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
502 LogUtil.v("InCallActivityCommon.handleDialerKeyDown", "keyCode %d, event: %s", keyCode, event);
503
504 // As soon as the user starts typing valid dialable keys on the
505 // keyboard (presumably to type DTMF tones) we start passing the
506 // key events to the DTMFDialer's onDialerKeyDown.
linyuh69a25062017-11-15 16:18:51 -0800507 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800508 if (dialpadFragment != null && dialpadFragment.isVisible()) {
509 return dialpadFragment.onDialerKeyDown(event);
510 }
511
512 return false;
513 }
514
Eric Erfanianccca3152017-02-22 16:32:36 -0800515 public void showPostCharWaitDialog(String callId, String chars) {
516 if (inCallActivity.isVisible()) {
517 PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
518 fragment.show(inCallActivity.getSupportFragmentManager(), "postCharWait");
519
520 showPostCharWaitDialogOnResume = false;
521 showPostCharWaitDialogCallId = null;
522 showPostCharWaitDialogChars = null;
523 } else {
524 showPostCharWaitDialogOnResume = true;
525 showPostCharWaitDialogCallId = callId;
526 showPostCharWaitDialogChars = chars;
527 }
528 }
529
Eric Erfanianccca3152017-02-22 16:32:36 -0800530 /**
531 * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
532 * be shown on launch.
533 *
534 * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and {@code
535 * false} to indicate no change should be made to the dialpad visibility.
536 */
537 private void relaunchedFromDialer(boolean showDialpad) {
538 showDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
539 animateDialpadOnShow = true;
540
541 if (showDialpadRequest == DIALPAD_REQUEST_SHOW) {
542 // If there's only one line in use, AND it's on hold, then we're sure the user
543 // wants to use the dialpad toward the exact line, so un-hold the holding line.
544 DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
545 if (call != null && call.getState() == State.ONHOLD) {
546 call.unhold();
547 }
548 }
549 }
550
Eric Erfanianccca3152017-02-22 16:32:36 -0800551 public void setExcludeFromRecents(boolean exclude) {
552 List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
553 int taskId = inCallActivity.getTaskId();
554 for (int i = 0; i < tasks.size(); i++) {
555 ActivityManager.AppTask task = tasks.get(i);
556 try {
557 if (task.getTaskInfo().id == taskId) {
558 task.setExcludeFromRecents(exclude);
559 }
560 } catch (RuntimeException e) {
561 LogUtil.e(
562 "InCallActivityCommon.setExcludeFromRecents",
563 "RuntimeException when excluding task from recents.",
564 e);
565 }
566 }
567 }
568
Eric Erfanian938468d2017-10-24 14:05:52 -0700569 void updateNavigationBar(boolean isDialpadVisible) {
wangqifad3d872017-10-25 13:15:23 -0700570 if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) {
Eric Erfanian938468d2017-10-24 14:05:52 -0700571 View navigationBarBackground =
572 inCallActivity.getWindow().findViewById(R.id.navigation_bar_background);
573 if (navigationBarBackground != null) {
574 navigationBarBackground.setVisibility(isDialpadVisible ? View.VISIBLE : View.GONE);
575 }
576 }
577 }
578
Eric Erfanianccca3152017-02-22 16:32:36 -0800579 public boolean showDialpadFragment(boolean show, boolean animate) {
580 // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
linyuh69a25062017-11-15 16:18:51 -0800581 boolean isDialpadVisible = inCallActivity.isDialpadVisible();
Eric Erfanianccca3152017-02-22 16:32:36 -0800582 LogUtil.i(
583 "InCallActivityCommon.showDialpadFragment",
584 "show: %b, animate: %b, " + "isDialpadVisible: %b",
585 show,
586 animate,
587 isDialpadVisible);
588 if (show == isDialpadVisible) {
589 return false;
590 }
591
592 FragmentManager dialpadFragmentManager = inCallActivity.getDialpadFragmentManager();
593 if (dialpadFragmentManager == null) {
594 LogUtil.i(
595 "InCallActivityCommon.showDialpadFragment", "unable to show or hide dialpad fragment");
596 return false;
597 }
598
599 // We don't do a FragmentTransaction on the hide case because it will be dealt with when
600 // the listener is fired after an animation finishes.
601 if (!animate) {
602 if (show) {
603 performShowDialpadFragment(dialpadFragmentManager);
604 } else {
605 performHideDialpadFragment();
606 }
607 } else {
608 if (show) {
609 performShowDialpadFragment(dialpadFragmentManager);
linyuh69a25062017-11-15 16:18:51 -0800610 inCallActivity.getDialpadFragment().animateShowDialpad();
Eric Erfanianccca3152017-02-22 16:32:36 -0800611 }
linyuh69a25062017-11-15 16:18:51 -0800612 inCallActivity
613 .getDialpadFragment()
Eric Erfanianccca3152017-02-22 16:32:36 -0800614 .getView()
615 .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
616 }
617
618 ProximitySensor sensor = InCallPresenter.getInstance().getProximitySensor();
619 if (sensor != null) {
620 sensor.onDialpadVisible(show);
621 }
622 showDialpadRequest = DIALPAD_REQUEST_NONE;
623 return true;
624 }
625
626 private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
627 FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
linyuh69a25062017-11-15 16:18:51 -0800628 DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
Eric Erfanianccca3152017-02-22 16:32:36 -0800629 if (dialpadFragment == null) {
630 transaction.add(
631 inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
632 } else {
633 transaction.show(dialpadFragment);
634 }
635
636 transaction.commitAllowingStateLoss();
637 dialpadFragmentManager.executePendingTransactions();
638
639 Logger.get(inCallActivity).logScreenView(ScreenEvent.Type.INCALL_DIALPAD, inCallActivity);
Eric Erfanian938468d2017-10-24 14:05:52 -0700640 updateNavigationBar(true /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800641 }
642
643 private void performHideDialpadFragment() {
644 FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
645 if (fragmentManager == null) {
646 LogUtil.e(
647 "InCallActivityCommon.performHideDialpadFragment", "child fragment manager is null");
648 return;
649 }
650
651 Fragment fragment = fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
652 if (fragment != null) {
653 FragmentTransaction transaction = fragmentManager.beginTransaction();
654 transaction.hide(fragment);
655 transaction.commitAllowingStateLoss();
656 fragmentManager.executePendingTransactions();
657 }
Eric Erfanian938468d2017-10-24 14:05:52 -0700658 updateNavigationBar(false /* isDialpadVisible */);
Eric Erfanianccca3152017-02-22 16:32:36 -0800659 }
660
Eric Erfanianccca3152017-02-22 16:32:36 -0800661 public void updateTaskDescription() {
662 Resources resources = inCallActivity.getResources();
663 int color;
664 if (resources.getBoolean(R.bool.is_layout_landscape)) {
665 color =
666 ResourcesCompat.getColor(
667 resources, R.color.statusbar_background_color, inCallActivity.getTheme());
668 } else {
669 color = InCallPresenter.getInstance().getThemeColorManager().getSecondaryColor();
670 }
671
672 TaskDescription td =
673 new TaskDescription(resources.getString(R.string.notification_ongoing_call), null, color);
674 inCallActivity.setTaskDescription(td);
675 }
676
Eric Erfanianccca3152017-02-22 16:32:36 -0800677 private void internalResolveIntent(Intent intent) {
678 if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
679 return;
680 }
681
682 if (intent.hasExtra(INTENT_EXTRA_SHOW_DIALPAD)) {
683 // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
684 // dialpad should be initially visible. If the extra isn't
685 // present at all, we just leave the dialpad in its previous state.
686 boolean showDialpad = intent.getBooleanExtra(INTENT_EXTRA_SHOW_DIALPAD, false);
687 LogUtil.i("InCallActivityCommon.internalResolveIntent", "SHOW_DIALPAD_EXTRA: " + showDialpad);
688
689 relaunchedFromDialer(showDialpad);
690 }
691
692 DialerCall outgoingCall = CallList.getInstance().getOutgoingCall();
693 if (outgoingCall == null) {
694 outgoingCall = CallList.getInstance().getPendingOutgoingCall();
695 }
696
Eric Erfanianccca3152017-02-22 16:32:36 -0800697 if (intent.getBooleanExtra(INTENT_EXTRA_NEW_OUTGOING_CALL, false)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800698 intent.removeExtra(INTENT_EXTRA_NEW_OUTGOING_CALL);
699
700 // InCallActivity is responsible for disconnecting a new outgoing call if there
701 // is no way of making it (i.e. no valid call capable accounts).
702 // If the version is not MSIM compatible, then ignore this code.
703 if (CompatUtils.isMSIMCompatible()
704 && InCallPresenter.isCallWithNoValidAccounts(outgoingCall)) {
705 LogUtil.i(
706 "InCallActivityCommon.internalResolveIntent",
707 "call with no valid accounts, disconnecting");
708 outgoingCall.disconnect();
709 }
710
linyuh9c327da2017-11-14 12:33:48 -0800711 inCallActivity.dismissKeyguard(true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 }
713
714 boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700715 if (didShowAccountSelectionDialog) {
716 inCallActivity.hideMainInCallFragment();
717 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800718 }
719
720 private boolean maybeShowAccountSelectionDialog() {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700721 DialerCall waitingForAccountCall = CallList.getInstance().getWaitingForAccountCall();
722 if (waitingForAccountCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800723 return false;
724 }
725
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700726 Bundle extras = waitingForAccountCall.getIntentExtras();
Eric Erfanianccca3152017-02-22 16:32:36 -0800727 List<PhoneAccountHandle> phoneAccountHandles;
728 if (extras != null) {
729 phoneAccountHandles =
730 extras.getParcelableArrayList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS);
731 } else {
732 phoneAccountHandles = new ArrayList<>();
733 }
734
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700735 selectPhoneAccountDialogFragment =
Eric Erfanianccca3152017-02-22 16:32:36 -0800736 SelectPhoneAccountDialogFragment.newInstance(
737 R.string.select_phone_account_for_calls,
738 true,
739 phoneAccountHandles,
740 selectAccountListener,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700741 waitingForAccountCall.getId());
742 selectPhoneAccountDialogFragment.show(
743 inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
Eric Erfanianccca3152017-02-22 16:32:36 -0800744 return true;
745 }
linyuhf99f6302017-11-15 11:23:51 -0800746
747 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
748 @Deprecated
749 @Nullable
750 Dialog getErrorDialog() {
751 return errorDialog;
752 }
753
754 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
755 @Deprecated
756 void setErrorDialog(@Nullable Dialog errorDialog) {
757 this.errorDialog = errorDialog;
758 }
759
760 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
761 @Deprecated
762 @Nullable
763 SelectPhoneAccountDialogFragment getSelectPhoneAccountDialogFragment() {
764 return selectPhoneAccountDialogFragment;
765 }
766
767 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
768 @Deprecated
769 void setSelectPhoneAccountDialogFragment(
770 @Nullable SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment) {
771 this.selectPhoneAccountDialogFragment = selectPhoneAccountDialogFragment;
772 }
linyuh7b86f562017-11-16 11:24:09 -0800773
774 /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
775 @Deprecated
776 InternationalCallOnWifiDialogFragment.Callback getCallbackForInternationalCallOnWifiDialog() {
777 return internationalCallOnWifiCallback;
778 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800779}