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