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