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.video.impl; 18 19 import android.Manifest.permission; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.graphics.Point; 23 import android.graphics.drawable.Animatable; 24 import android.os.Bundle; 25 import android.support.annotation.ColorInt; 26 import android.support.annotation.NonNull; 27 import android.support.annotation.Nullable; 28 import android.support.annotation.VisibleForTesting; 29 import android.support.v4.app.Fragment; 30 import android.support.v4.app.FragmentTransaction; 31 import android.support.v4.view.animation.FastOutLinearInInterpolator; 32 import android.support.v4.view.animation.LinearOutSlowInInterpolator; 33 import android.telecom.CallAudioState; 34 import android.text.TextUtils; 35 import android.view.LayoutInflater; 36 import android.view.Surface; 37 import android.view.SurfaceView; 38 import android.view.View; 39 import android.view.View.OnClickListener; 40 import android.view.View.OnSystemUiVisibilityChangeListener; 41 import android.view.ViewGroup; 42 import android.view.ViewGroup.MarginLayoutParams; 43 import android.view.ViewTreeObserver; 44 import android.view.accessibility.AccessibilityEvent; 45 import android.view.animation.AccelerateDecelerateInterpolator; 46 import android.view.animation.Interpolator; 47 import android.widget.FrameLayout; 48 import android.widget.ImageButton; 49 import android.widget.TextView; 50 import com.android.dialer.common.Assert; 51 import com.android.dialer.common.FragmentUtils; 52 import com.android.dialer.common.LogUtil; 53 import com.android.dialer.util.PermissionsUtil; 54 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment; 55 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter; 56 import com.android.incallui.contactgrid.ContactGridManager; 57 import com.android.incallui.hold.OnHoldFragment; 58 import com.android.incallui.incall.protocol.InCallButtonIds; 59 import com.android.incallui.incall.protocol.InCallButtonIdsExtension; 60 import com.android.incallui.incall.protocol.InCallButtonUi; 61 import com.android.incallui.incall.protocol.InCallButtonUiDelegate; 62 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; 63 import com.android.incallui.incall.protocol.InCallScreen; 64 import com.android.incallui.incall.protocol.InCallScreenDelegate; 65 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; 66 import com.android.incallui.incall.protocol.PrimaryCallState; 67 import com.android.incallui.incall.protocol.PrimaryInfo; 68 import com.android.incallui.incall.protocol.SecondaryInfo; 69 import com.android.incallui.video.impl.CheckableImageButton.OnCheckedChangeListener; 70 import com.android.incallui.video.protocol.VideoCallScreen; 71 import com.android.incallui.video.protocol.VideoCallScreenDelegate; 72 import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory; 73 import com.android.incallui.videotech.utils.VideoUtils; 74 75 /** 76 * Contains UI elements for a video call. 77 * 78 * <p>This version is used by RCS Video Share since Dreamchip requires a SurfaceView instead of the 79 * TextureView, which is present in {@link VideoCallFragment} and used by IMS. 80 */ 81 public class SurfaceViewVideoCallFragment extends Fragment 82 implements InCallScreen, 83 InCallButtonUi, 84 VideoCallScreen, 85 OnClickListener, 86 OnCheckedChangeListener, 87 AudioRouteSelectorPresenter, 88 OnSystemUiVisibilityChangeListener { 89 90 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 91 static final String ARG_CALL_ID = "call_id"; 92 93 private static final int CAMERA_PERMISSION_REQUEST_CODE = 1; 94 private static final long CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS = 2000L; 95 private static final long VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS = 2000L; 96 97 private InCallScreenDelegate inCallScreenDelegate; 98 private VideoCallScreenDelegate videoCallScreenDelegate; 99 private InCallButtonUiDelegate inCallButtonUiDelegate; 100 private View endCallButton; 101 private CheckableImageButton speakerButton; 102 private SpeakerButtonController speakerButtonController; 103 private CheckableImageButton muteButton; 104 private CheckableImageButton cameraOffButton; 105 private ImageButton swapCameraButton; 106 private View switchOnHoldButton; 107 private View onHoldContainer; 108 private SwitchOnHoldCallController switchOnHoldCallController; 109 private TextView remoteVideoOff; 110 private View mutePreviewOverlay; 111 private View previewOffOverlay; 112 private View controls; 113 private View controlsContainer; 114 private SurfaceView previewSurfaceView; 115 private SurfaceView remoteSurfaceView; 116 private View greenScreenBackgroundView; 117 private View fullscreenBackgroundView; 118 private FrameLayout previewRoot; 119 private boolean shouldShowRemote; 120 private boolean shouldShowPreview; 121 private boolean isInFullscreenMode; 122 private boolean isInGreenScreenMode; 123 private boolean hasInitializedScreenModes; 124 private boolean isRemotelyHeld; 125 private ContactGridManager contactGridManager; 126 private SecondaryInfo savedSecondaryInfo; 127 private final Runnable cameraPermissionDialogRunnable = 128 new Runnable() { 129 @Override 130 public void run() { 131 if (videoCallScreenDelegate.shouldShowCameraPermissionToast()) { 132 LogUtil.i( 133 "SurfaceViewVideoCallFragment.cameraPermissionDialogRunnable", "showing dialog"); 134 checkCameraPermission(); 135 } 136 } 137 }; 138 newInstance(String callId)139 public static SurfaceViewVideoCallFragment newInstance(String callId) { 140 Bundle bundle = new Bundle(); 141 bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId)); 142 143 SurfaceViewVideoCallFragment instance = new SurfaceViewVideoCallFragment(); 144 instance.setArguments(bundle); 145 return instance; 146 } 147 148 @Override onCreate(@ullable Bundle savedInstanceState)149 public void onCreate(@Nullable Bundle savedInstanceState) { 150 super.onCreate(savedInstanceState); 151 LogUtil.i("SurfaceViewVideoCallFragment.onCreate", null); 152 153 inCallButtonUiDelegate = 154 FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class) 155 .newInCallButtonUiDelegate(); 156 if (savedInstanceState != null) { 157 inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState); 158 } 159 } 160 161 @Override onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)162 public void onRequestPermissionsResult( 163 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 164 if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { 165 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 166 LogUtil.i( 167 "SurfaceViewVideoCallFragment.onRequestPermissionsResult", 168 "Camera permission granted."); 169 videoCallScreenDelegate.onCameraPermissionGranted(); 170 } else { 171 LogUtil.i( 172 "SurfaceViewVideoCallFragment.onRequestPermissionsResult", "Camera permission denied."); 173 } 174 } 175 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 176 } 177 178 @Nullable 179 @Override onCreateView( LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle)180 public View onCreateView( 181 LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) { 182 LogUtil.i("SurfaceViewVideoCallFragment.onCreateView", null); 183 184 View view = layoutInflater.inflate(R.layout.frag_videocall_surfaceview, viewGroup, false); 185 contactGridManager = 186 new ContactGridManager(view, null /* no avatar */, 0, false /* showAnonymousAvatar */); 187 188 controls = view.findViewById(R.id.videocall_video_controls); 189 controls.setVisibility(getActivity().isInMultiWindowMode() ? View.GONE : View.VISIBLE); 190 controlsContainer = view.findViewById(R.id.videocall_video_controls_container); 191 speakerButton = (CheckableImageButton) view.findViewById(R.id.videocall_speaker_button); 192 muteButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_button); 193 muteButton.setOnCheckedChangeListener(this); 194 mutePreviewOverlay = view.findViewById(R.id.videocall_video_preview_mute_overlay); 195 cameraOffButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_video); 196 cameraOffButton.setOnCheckedChangeListener(this); 197 previewOffOverlay = view.findViewById(R.id.videocall_video_preview_off_overlay); 198 swapCameraButton = (ImageButton) view.findViewById(R.id.videocall_switch_video); 199 swapCameraButton.setOnClickListener(this); 200 view.findViewById(R.id.videocall_switch_controls) 201 .setVisibility(getActivity().isInMultiWindowMode() ? View.GONE : View.VISIBLE); 202 switchOnHoldButton = view.findViewById(R.id.videocall_switch_on_hold); 203 onHoldContainer = view.findViewById(R.id.videocall_on_hold_banner); 204 remoteVideoOff = (TextView) view.findViewById(R.id.videocall_remote_video_off); 205 remoteVideoOff.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 206 endCallButton = view.findViewById(R.id.videocall_end_call); 207 endCallButton.setOnClickListener(this); 208 previewSurfaceView = (SurfaceView) view.findViewById(R.id.videocall_video_preview); 209 previewSurfaceView.setZOrderMediaOverlay(true); 210 previewOffOverlay.setOnClickListener( 211 new OnClickListener() { 212 @Override 213 public void onClick(View v) { 214 checkCameraPermission(); 215 } 216 }); 217 remoteSurfaceView = (SurfaceView) view.findViewById(R.id.videocall_video_remote); 218 remoteSurfaceView.setOnClickListener( 219 surfaceView -> { 220 videoCallScreenDelegate.resetAutoFullscreenTimer(); 221 if (isInFullscreenMode) { 222 updateFullscreenAndGreenScreenMode( 223 false /* shouldShowFullscreen */, false /* shouldShowGreenScreen */); 224 } else { 225 updateFullscreenAndGreenScreenMode( 226 true /* shouldShowFullscreen */, false /* shouldShowGreenScreen */); 227 } 228 }); 229 greenScreenBackgroundView = view.findViewById(R.id.videocall_green_screen_background); 230 fullscreenBackgroundView = view.findViewById(R.id.videocall_fullscreen_background); 231 previewRoot = (FrameLayout) view.findViewById(R.id.videocall_preview_root); 232 233 // We need the texture view size to be able to scale the remote video. At this point the view 234 // layout won't be complete so add a layout listener. 235 ViewTreeObserver observer = remoteSurfaceView.getViewTreeObserver(); 236 observer.addOnGlobalLayoutListener( 237 new ViewTreeObserver.OnGlobalLayoutListener() { 238 @Override 239 public void onGlobalLayout() { 240 LogUtil.i("SurfaceViewVideoCallFragment.onGlobalLayout", null); 241 updateVideoOffViews(); 242 // Remove the listener so we don't continually re-layout. 243 ViewTreeObserver observer = remoteSurfaceView.getViewTreeObserver(); 244 if (observer.isAlive()) { 245 observer.removeOnGlobalLayoutListener(this); 246 } 247 } 248 }); 249 250 return view; 251 } 252 253 @Override onViewCreated(View view, @Nullable Bundle bundle)254 public void onViewCreated(View view, @Nullable Bundle bundle) { 255 super.onViewCreated(view, bundle); 256 LogUtil.i("SurfaceViewVideoCallFragment.onViewCreated", null); 257 258 inCallScreenDelegate = 259 FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class) 260 .newInCallScreenDelegate(); 261 videoCallScreenDelegate = 262 FragmentUtils.getParentUnsafe(this, VideoCallScreenDelegateFactory.class) 263 .newVideoCallScreenDelegate(this); 264 265 speakerButtonController = 266 new SpeakerButtonController(speakerButton, inCallButtonUiDelegate, videoCallScreenDelegate); 267 switchOnHoldCallController = 268 new SwitchOnHoldCallController( 269 switchOnHoldButton, onHoldContainer, inCallScreenDelegate, videoCallScreenDelegate); 270 271 videoCallScreenDelegate.initVideoCallScreenDelegate(getContext(), this); 272 273 inCallScreenDelegate.onInCallScreenDelegateInit(this); 274 inCallScreenDelegate.onInCallScreenReady(); 275 inCallButtonUiDelegate.onInCallButtonUiReady(this); 276 277 view.setOnSystemUiVisibilityChangeListener(this); 278 } 279 280 @Override onSaveInstanceState(Bundle outState)281 public void onSaveInstanceState(Bundle outState) { 282 super.onSaveInstanceState(outState); 283 inCallButtonUiDelegate.onSaveInstanceState(outState); 284 } 285 286 @Override onDestroyView()287 public void onDestroyView() { 288 super.onDestroyView(); 289 LogUtil.i("SurfaceViewVideoCallFragment.onDestroyView", null); 290 inCallButtonUiDelegate.onInCallButtonUiUnready(); 291 inCallScreenDelegate.onInCallScreenUnready(); 292 } 293 294 @Override onAttach(Context context)295 public void onAttach(Context context) { 296 super.onAttach(context); 297 if (savedSecondaryInfo != null) { 298 setSecondary(savedSecondaryInfo); 299 } 300 } 301 302 @Override onStart()303 public void onStart() { 304 super.onStart(); 305 LogUtil.i("SurfaceViewVideoCallFragment.onStart", null); 306 onVideoScreenStart(); 307 } 308 309 @Override onVideoScreenStart()310 public void onVideoScreenStart() { 311 videoCallScreenDelegate.onVideoCallScreenUiReady(); 312 getView().postDelayed(cameraPermissionDialogRunnable, CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS); 313 } 314 315 @Override onResume()316 public void onResume() { 317 super.onResume(); 318 LogUtil.i("SurfaceViewVideoCallFragment.onResume", null); 319 inCallScreenDelegate.onInCallScreenResumed(); 320 } 321 322 @Override onPause()323 public void onPause() { 324 super.onPause(); 325 LogUtil.i("SurfaceViewVideoCallFragment.onPause", null); 326 inCallScreenDelegate.onInCallScreenPaused(); 327 } 328 329 @Override onStop()330 public void onStop() { 331 super.onStop(); 332 LogUtil.i("SurfaceViewVideoCallFragment.onStop", null); 333 onVideoScreenStop(); 334 } 335 336 @Override onVideoScreenStop()337 public void onVideoScreenStop() { 338 getView().removeCallbacks(cameraPermissionDialogRunnable); 339 videoCallScreenDelegate.onVideoCallScreenUiUnready(); 340 } 341 exitFullscreenMode()342 private void exitFullscreenMode() { 343 LogUtil.i("SurfaceViewVideoCallFragment.exitFullscreenMode", null); 344 345 if (!getView().isAttachedToWindow()) { 346 LogUtil.i("SurfaceViewVideoCallFragment.exitFullscreenMode", "not attached"); 347 return; 348 } 349 350 showSystemUI(); 351 352 LinearOutSlowInInterpolator linearOutSlowInInterpolator = new LinearOutSlowInInterpolator(); 353 354 // Animate the controls to the shown state. 355 controls 356 .animate() 357 .translationX(0) 358 .translationY(0) 359 .setInterpolator(linearOutSlowInInterpolator) 360 .alpha(1) 361 .start(); 362 363 // Animate onHold to the shown state. 364 switchOnHoldButton 365 .animate() 366 .translationX(0) 367 .translationY(0) 368 .setInterpolator(linearOutSlowInInterpolator) 369 .alpha(1) 370 .withStartAction( 371 new Runnable() { 372 @Override 373 public void run() { 374 switchOnHoldCallController.setOnScreen(); 375 } 376 }); 377 378 View contactGridView = contactGridManager.getContainerView(); 379 // Animate contact grid to the shown state. 380 contactGridView 381 .animate() 382 .translationX(0) 383 .translationY(0) 384 .setInterpolator(linearOutSlowInInterpolator) 385 .alpha(1) 386 .withStartAction( 387 new Runnable() { 388 @Override 389 public void run() { 390 contactGridManager.show(); 391 } 392 }); 393 394 endCallButton 395 .animate() 396 .translationX(0) 397 .translationY(0) 398 .setInterpolator(linearOutSlowInInterpolator) 399 .alpha(1) 400 .withStartAction( 401 new Runnable() { 402 @Override 403 public void run() { 404 endCallButton.setVisibility(View.VISIBLE); 405 } 406 }) 407 .start(); 408 409 // Animate all the preview controls up to make room for the navigation bar. 410 // In green screen mode we don't need this because the preview takes up the whole screen and has 411 // a fixed position. 412 if (!isInGreenScreenMode) { 413 Point previewOffsetStartShown = getPreviewOffsetStartShown(); 414 for (View view : getAllPreviewRelatedViews()) { 415 // Animate up with the preview offset above the navigation bar. 416 view.animate() 417 .translationX(previewOffsetStartShown.x) 418 .translationY(previewOffsetStartShown.y) 419 .setInterpolator(new AccelerateDecelerateInterpolator()) 420 .start(); 421 } 422 } 423 424 updateOverlayBackground(); 425 } 426 showSystemUI()427 private void showSystemUI() { 428 View view = getView(); 429 if (view != null) { 430 // Code is more expressive with all flags present, even though some may be combined 431 //noinspection PointlessBitwiseExpression 432 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 433 } 434 } 435 436 /** Set view flags to hide the system UI. System UI will return on any touch event */ hideSystemUI()437 private void hideSystemUI() { 438 View view = getView(); 439 if (view != null) { 440 view.setSystemUiVisibility( 441 View.SYSTEM_UI_FLAG_FULLSCREEN 442 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 443 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 444 } 445 } 446 getControlsOffsetEndHidden(View controls)447 private Point getControlsOffsetEndHidden(View controls) { 448 if (isLandscape()) { 449 return new Point(0, getOffsetBottom(controls)); 450 } else { 451 return new Point(getOffsetStart(controls), 0); 452 } 453 } 454 getSwitchOnHoldOffsetEndHidden(View swapCallButton)455 private Point getSwitchOnHoldOffsetEndHidden(View swapCallButton) { 456 if (isLandscape()) { 457 return new Point(0, getOffsetTop(swapCallButton)); 458 } else { 459 return new Point(getOffsetEnd(swapCallButton), 0); 460 } 461 } 462 getContactGridOffsetEndHidden(View view)463 private Point getContactGridOffsetEndHidden(View view) { 464 return new Point(0, getOffsetTop(view)); 465 } 466 getEndCallOffsetEndHidden(View endCallButton)467 private Point getEndCallOffsetEndHidden(View endCallButton) { 468 if (isLandscape()) { 469 return new Point(getOffsetEnd(endCallButton), 0); 470 } else { 471 return new Point(0, ((MarginLayoutParams) endCallButton.getLayoutParams()).bottomMargin); 472 } 473 } 474 getPreviewOffsetStartShown()475 private Point getPreviewOffsetStartShown() { 476 // No insets in multiwindow mode, and rootWindowInsets will get the display's insets. 477 if (getActivity().isInMultiWindowMode()) { 478 return new Point(); 479 } 480 if (isLandscape()) { 481 int stableInsetEnd = 482 getView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL 483 ? getView().getRootWindowInsets().getStableInsetLeft() 484 : -getView().getRootWindowInsets().getStableInsetRight(); 485 return new Point(stableInsetEnd, 0); 486 } else { 487 return new Point(0, -getView().getRootWindowInsets().getStableInsetBottom()); 488 } 489 } 490 getAllPreviewRelatedViews()491 private View[] getAllPreviewRelatedViews() { 492 return new View[] {previewRoot, mutePreviewOverlay}; 493 } 494 getOffsetTop(View view)495 private int getOffsetTop(View view) { 496 return -(view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).topMargin); 497 } 498 getOffsetBottom(View view)499 private int getOffsetBottom(View view) { 500 return view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).bottomMargin; 501 } 502 getOffsetStart(View view)503 private int getOffsetStart(View view) { 504 int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginStart(); 505 if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 506 offset = -offset; 507 } 508 return -offset; 509 } 510 getOffsetEnd(View view)511 private int getOffsetEnd(View view) { 512 int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginEnd(); 513 if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 514 offset = -offset; 515 } 516 return offset; 517 } 518 enterFullscreenMode()519 private void enterFullscreenMode() { 520 LogUtil.i("SurfaceViewVideoCallFragment.enterFullscreenMode", null); 521 522 hideSystemUI(); 523 524 Interpolator fastOutLinearInInterpolator = new FastOutLinearInInterpolator(); 525 526 // Animate controls to the hidden state. 527 Point offset = getControlsOffsetEndHidden(controls); 528 controls 529 .animate() 530 .translationX(offset.x) 531 .translationY(offset.y) 532 .setInterpolator(fastOutLinearInInterpolator) 533 .alpha(0) 534 .start(); 535 536 // Animate onHold to the hidden state. 537 offset = getSwitchOnHoldOffsetEndHidden(switchOnHoldButton); 538 switchOnHoldButton 539 .animate() 540 .translationX(offset.x) 541 .translationY(offset.y) 542 .setInterpolator(fastOutLinearInInterpolator) 543 .alpha(0); 544 545 View contactGridView = contactGridManager.getContainerView(); 546 // Animate contact grid to the hidden state. 547 offset = getContactGridOffsetEndHidden(contactGridView); 548 contactGridView 549 .animate() 550 .translationX(offset.x) 551 .translationY(offset.y) 552 .setInterpolator(fastOutLinearInInterpolator) 553 .alpha(0); 554 555 offset = getEndCallOffsetEndHidden(endCallButton); 556 // Use a fast out interpolator to quickly fade out the button. This is important because the 557 // button can't draw under the navigation bar which means that it'll look weird if it just 558 // abruptly disappears when it reaches the edge of the naivgation bar. 559 endCallButton 560 .animate() 561 .translationX(offset.x) 562 .translationY(offset.y) 563 .setInterpolator(fastOutLinearInInterpolator) 564 .alpha(0) 565 .withEndAction( 566 new Runnable() { 567 @Override 568 public void run() { 569 endCallButton.setVisibility(View.INVISIBLE); 570 } 571 }) 572 .setInterpolator(new FastOutLinearInInterpolator()) 573 .start(); 574 575 // Animate all the preview controls down now that the navigation bar is hidden. 576 // In green screen mode we don't need this because the preview takes up the whole screen and has 577 // a fixed position. 578 if (!isInGreenScreenMode) { 579 for (View view : getAllPreviewRelatedViews()) { 580 // Animate down with the navigation bar hidden. 581 view.animate() 582 .translationX(0) 583 .translationY(0) 584 .setInterpolator(new AccelerateDecelerateInterpolator()) 585 .start(); 586 } 587 } 588 updateOverlayBackground(); 589 } 590 591 @Override onClick(View v)592 public void onClick(View v) { 593 if (v == endCallButton) { 594 LogUtil.i("SurfaceViewVideoCallFragment.onClick", "end call button clicked"); 595 inCallButtonUiDelegate.onEndCallClicked(); 596 videoCallScreenDelegate.resetAutoFullscreenTimer(); 597 } else if (v == swapCameraButton) { 598 if (swapCameraButton.getDrawable() instanceof Animatable) { 599 ((Animatable) swapCameraButton.getDrawable()).start(); 600 } 601 inCallButtonUiDelegate.toggleCameraClicked(); 602 videoCallScreenDelegate.resetAutoFullscreenTimer(); 603 } 604 } 605 606 @Override onCheckedChanged(CheckableImageButton button, boolean isChecked)607 public void onCheckedChanged(CheckableImageButton button, boolean isChecked) { 608 if (button == cameraOffButton) { 609 if (!isChecked && !VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) { 610 LogUtil.i("SurfaceViewVideoCallFragment.onCheckedChanged", "show camera permission dialog"); 611 checkCameraPermission(); 612 } else { 613 inCallButtonUiDelegate.pauseVideoClicked(isChecked); 614 videoCallScreenDelegate.resetAutoFullscreenTimer(); 615 } 616 } else if (button == muteButton) { 617 inCallButtonUiDelegate.muteClicked(isChecked, true /* clickedByUser */); 618 videoCallScreenDelegate.resetAutoFullscreenTimer(); 619 } 620 } 621 622 @Override showVideoViews( boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld)623 public void showVideoViews( 624 boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld) { 625 LogUtil.i( 626 "SurfaceViewVideoCallFragment.showVideoViews", 627 "showPreview: %b, shouldShowRemote: %b", 628 shouldShowPreview, 629 shouldShowRemote); 630 631 this.shouldShowPreview = shouldShowPreview; 632 this.shouldShowRemote = shouldShowRemote; 633 this.isRemotelyHeld = isRemotelyHeld; 634 635 previewSurfaceView.setVisibility(shouldShowPreview ? View.VISIBLE : View.INVISIBLE); 636 637 videoCallScreenDelegate.setSurfaceViews(previewSurfaceView, remoteSurfaceView); 638 updateVideoOffViews(); 639 } 640 641 @Override onLocalVideoDimensionsChanged()642 public void onLocalVideoDimensionsChanged() { 643 LogUtil.i("SurfaceViewVideoCallFragment.onLocalVideoDimensionsChanged", null); 644 } 645 646 @Override onLocalVideoOrientationChanged()647 public void onLocalVideoOrientationChanged() { 648 LogUtil.i("SurfaceViewVideoCallFragment.onLocalVideoOrientationChanged", null); 649 } 650 651 /** Called when the remote video's dimensions change. */ 652 @Override onRemoteVideoDimensionsChanged()653 public void onRemoteVideoDimensionsChanged() { 654 LogUtil.i("SurfaceViewVideoCallFragment.onRemoteVideoDimensionsChanged", null); 655 } 656 657 @Override updateFullscreenAndGreenScreenMode( boolean shouldShowFullscreen, boolean shouldShowGreenScreen)658 public void updateFullscreenAndGreenScreenMode( 659 boolean shouldShowFullscreen, boolean shouldShowGreenScreen) { 660 LogUtil.i( 661 "SurfaceViewVideoCallFragment.updateFullscreenAndGreenScreenMode", 662 "shouldShowFullscreen: %b, shouldShowGreenScreen: %b", 663 shouldShowFullscreen, 664 shouldShowGreenScreen); 665 666 if (getActivity() == null) { 667 LogUtil.i( 668 "SurfaceViewVideoCallFragment.updateFullscreenAndGreenScreenMode", 669 "not attached to activity"); 670 return; 671 } 672 673 // Check if anything is actually going to change. The first time this function is called we 674 // force a change by checking the hasInitializedScreenModes flag. We also force both fullscreen 675 // and green screen modes to update even if only one has changed. That's because they both 676 // depend on each other. 677 if (hasInitializedScreenModes 678 && shouldShowGreenScreen == isInGreenScreenMode 679 && shouldShowFullscreen == isInFullscreenMode) { 680 LogUtil.i( 681 "SurfaceViewVideoCallFragment.updateFullscreenAndGreenScreenMode", 682 "no change to screen modes"); 683 return; 684 } 685 hasInitializedScreenModes = true; 686 isInGreenScreenMode = shouldShowGreenScreen; 687 isInFullscreenMode = shouldShowFullscreen; 688 689 if (getView().isAttachedToWindow() && !getActivity().isInMultiWindowMode()) { 690 controlsContainer.onApplyWindowInsets(getView().getRootWindowInsets()); 691 } 692 if (shouldShowGreenScreen) { 693 enterGreenScreenMode(); 694 } else { 695 exitGreenScreenMode(); 696 } 697 if (shouldShowFullscreen) { 698 enterFullscreenMode(); 699 } else { 700 exitFullscreenMode(); 701 } 702 updateVideoOffViews(); 703 704 OnHoldFragment onHoldFragment = 705 ((OnHoldFragment) 706 getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner)); 707 if (onHoldFragment != null) { 708 onHoldFragment.setPadTopInset(!isInFullscreenMode); 709 } 710 } 711 712 @Override getVideoCallScreenFragment()713 public Fragment getVideoCallScreenFragment() { 714 return this; 715 } 716 717 @Override 718 @NonNull getCallId()719 public String getCallId() { 720 return Assert.isNotNull(getArguments().getString(ARG_CALL_ID)); 721 } 722 723 @Override onHandoverFromWiFiToLte()724 public void onHandoverFromWiFiToLte() {} 725 726 @Override showButton(@nCallButtonIds int buttonId, boolean show)727 public void showButton(@InCallButtonIds int buttonId, boolean show) { 728 LogUtil.v( 729 "SurfaceViewVideoCallFragment.showButton", 730 "buttonId: %s, show: %b", 731 InCallButtonIdsExtension.toString(buttonId), 732 show); 733 if (buttonId == InCallButtonIds.BUTTON_AUDIO) { 734 speakerButtonController.setEnabled(show); 735 } else if (buttonId == InCallButtonIds.BUTTON_MUTE) { 736 muteButton.setEnabled(show); 737 } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) { 738 cameraOffButton.setEnabled(show); 739 } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { 740 switchOnHoldCallController.setVisible(show); 741 } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_CAMERA) { 742 swapCameraButton.setEnabled(show); 743 } 744 } 745 746 @Override enableButton(@nCallButtonIds int buttonId, boolean enable)747 public void enableButton(@InCallButtonIds int buttonId, boolean enable) { 748 LogUtil.v( 749 "SurfaceViewVideoCallFragment.setEnabled", 750 "buttonId: %s, enable: %b", 751 InCallButtonIdsExtension.toString(buttonId), 752 enable); 753 if (buttonId == InCallButtonIds.BUTTON_AUDIO) { 754 speakerButtonController.setEnabled(enable); 755 } else if (buttonId == InCallButtonIds.BUTTON_MUTE) { 756 muteButton.setEnabled(enable); 757 } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) { 758 cameraOffButton.setEnabled(enable); 759 } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { 760 switchOnHoldCallController.setEnabled(enable); 761 } 762 } 763 764 @Override setEnabled(boolean enabled)765 public void setEnabled(boolean enabled) { 766 LogUtil.v("SurfaceViewVideoCallFragment.setEnabled", "enabled: " + enabled); 767 speakerButtonController.setEnabled(enabled); 768 muteButton.setEnabled(enabled); 769 cameraOffButton.setEnabled(enabled); 770 switchOnHoldCallController.setEnabled(enabled); 771 } 772 773 @Override setHold(boolean value)774 public void setHold(boolean value) { 775 LogUtil.i("SurfaceViewVideoCallFragment.setHold", "value: " + value); 776 } 777 778 @Override setCameraSwitched(boolean isBackFacingCamera)779 public void setCameraSwitched(boolean isBackFacingCamera) { 780 LogUtil.i( 781 "SurfaceViewVideoCallFragment.setCameraSwitched", 782 "isBackFacingCamera: " + isBackFacingCamera); 783 } 784 785 @Override setVideoPaused(boolean isPaused)786 public void setVideoPaused(boolean isPaused) { 787 LogUtil.i("SurfaceViewVideoCallFragment.setVideoPaused", "isPaused: " + isPaused); 788 cameraOffButton.setChecked(isPaused); 789 } 790 791 @Override setAudioState(CallAudioState audioState)792 public void setAudioState(CallAudioState audioState) { 793 LogUtil.i("SurfaceViewVideoCallFragment.setAudioState", "audioState: " + audioState); 794 speakerButtonController.setAudioState(audioState); 795 muteButton.setChecked(audioState.isMuted()); 796 updateMutePreviewOverlayVisibility(); 797 } 798 799 @Override updateButtonStates()800 public void updateButtonStates() { 801 LogUtil.i("SurfaceViewVideoCallFragment.updateButtonState", null); 802 speakerButtonController.updateButtonState(); 803 switchOnHoldCallController.updateButtonState(); 804 } 805 806 @Override updateInCallButtonUiColors(@olorInt int color)807 public void updateInCallButtonUiColors(@ColorInt int color) {} 808 809 @Override getInCallButtonUiFragment()810 public Fragment getInCallButtonUiFragment() { 811 return this; 812 } 813 814 @Override showAudioRouteSelector()815 public void showAudioRouteSelector() { 816 LogUtil.i("SurfaceViewVideoCallFragment.showAudioRouteSelector", null); 817 AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState()) 818 .show(getChildFragmentManager(), null); 819 } 820 821 @Override onAudioRouteSelected(int audioRoute)822 public void onAudioRouteSelected(int audioRoute) { 823 LogUtil.i("SurfaceViewVideoCallFragment.onAudioRouteSelected", "audioRoute: " + audioRoute); 824 inCallButtonUiDelegate.setAudioRoute(audioRoute); 825 } 826 827 @Override onAudioRouteSelectorDismiss()828 public void onAudioRouteSelectorDismiss() {} 829 830 @Override setPrimary(@onNull PrimaryInfo primaryInfo)831 public void setPrimary(@NonNull PrimaryInfo primaryInfo) { 832 LogUtil.i("SurfaceViewVideoCallFragment.setPrimary", primaryInfo.toString()); 833 contactGridManager.setPrimary(primaryInfo); 834 } 835 836 @Override setSecondary(@onNull SecondaryInfo secondaryInfo)837 public void setSecondary(@NonNull SecondaryInfo secondaryInfo) { 838 LogUtil.i("SurfaceViewVideoCallFragment.setSecondary", secondaryInfo.toString()); 839 if (!isAdded()) { 840 savedSecondaryInfo = secondaryInfo; 841 return; 842 } 843 savedSecondaryInfo = null; 844 switchOnHoldCallController.setSecondaryInfo(secondaryInfo); 845 updateButtonStates(); 846 FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); 847 Fragment oldBanner = getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner); 848 if (secondaryInfo.shouldShow()) { 849 OnHoldFragment onHoldFragment = OnHoldFragment.newInstance(secondaryInfo); 850 onHoldFragment.setPadTopInset(!isInFullscreenMode); 851 transaction.replace(R.id.videocall_on_hold_banner, onHoldFragment); 852 } else { 853 if (oldBanner != null) { 854 transaction.remove(oldBanner); 855 } 856 } 857 transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top); 858 transaction.commitAllowingStateLoss(); 859 } 860 861 @Override setCallState(@onNull PrimaryCallState primaryCallState)862 public void setCallState(@NonNull PrimaryCallState primaryCallState) { 863 LogUtil.i("SurfaceViewVideoCallFragment.setCallState", primaryCallState.toString()); 864 contactGridManager.setCallState(primaryCallState); 865 } 866 867 @Override setEndCallButtonEnabled(boolean enabled, boolean animate)868 public void setEndCallButtonEnabled(boolean enabled, boolean animate) { 869 LogUtil.i("SurfaceViewVideoCallFragment.setEndCallButtonEnabled", "enabled: " + enabled); 870 } 871 872 @Override showManageConferenceCallButton(boolean visible)873 public void showManageConferenceCallButton(boolean visible) { 874 LogUtil.i("SurfaceViewVideoCallFragment.showManageConferenceCallButton", "visible: " + visible); 875 } 876 877 @Override isManageConferenceVisible()878 public boolean isManageConferenceVisible() { 879 LogUtil.i("SurfaceViewVideoCallFragment.isManageConferenceVisible", null); 880 return false; 881 } 882 883 @Override dispatchPopulateAccessibilityEvent(AccessibilityEvent event)884 public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 885 contactGridManager.dispatchPopulateAccessibilityEvent(event); 886 } 887 888 @Override showNoteSentToast()889 public void showNoteSentToast() { 890 LogUtil.i("SurfaceViewVideoCallFragment.showNoteSentToast", null); 891 } 892 893 @Override updateInCallScreenColors()894 public void updateInCallScreenColors() { 895 LogUtil.i("SurfaceViewVideoCallFragment.updateColors", null); 896 } 897 898 @Override onInCallScreenDialpadVisibilityChange(boolean isShowing)899 public void onInCallScreenDialpadVisibilityChange(boolean isShowing) { 900 LogUtil.i("SurfaceViewVideoCallFragment.onInCallScreenDialpadVisibilityChange", null); 901 } 902 903 @Override getAnswerAndDialpadContainerResourceId()904 public int getAnswerAndDialpadContainerResourceId() { 905 return 0; 906 } 907 908 @Override getInCallScreenFragment()909 public Fragment getInCallScreenFragment() { 910 return this; 911 } 912 913 @Override isShowingLocationUi()914 public boolean isShowingLocationUi() { 915 return false; 916 } 917 918 @Override showLocationUi(Fragment locationUi)919 public void showLocationUi(Fragment locationUi) { 920 LogUtil.e( 921 "SurfaceViewVideoCallFragment.showLocationUi", "Emergency video calling not supported"); 922 // Do nothing 923 } 924 isLandscape()925 private boolean isLandscape() { 926 // Choose orientation based on display orientation, not window orientation 927 int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation(); 928 return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270; 929 } 930 enterGreenScreenMode()931 private void enterGreenScreenMode() { 932 LogUtil.i("SurfaceViewVideoCallFragment.enterGreenScreenMode", null); 933 updateOverlayBackground(); 934 contactGridManager.setIsMiddleRowVisible(true); 935 updateMutePreviewOverlayVisibility(); 936 } 937 exitGreenScreenMode()938 private void exitGreenScreenMode() { 939 LogUtil.i("SurfaceViewVideoCallFragment.exitGreenScreenMode", null); 940 updateOverlayBackground(); 941 contactGridManager.setIsMiddleRowVisible(false); 942 updateMutePreviewOverlayVisibility(); 943 } 944 updateVideoOffViews()945 private void updateVideoOffViews() { 946 // Always hide the preview off and remote off views in green screen mode. 947 boolean previewEnabled = isInGreenScreenMode || shouldShowPreview; 948 previewOffOverlay.setVisibility(previewEnabled ? View.GONE : View.VISIBLE); 949 950 boolean remoteEnabled = isInGreenScreenMode || shouldShowRemote; 951 boolean isResumed = remoteEnabled && !isRemotelyHeld; 952 if (isResumed) { 953 boolean wasRemoteVideoOff = 954 TextUtils.equals( 955 remoteVideoOff.getText(), 956 remoteVideoOff.getResources().getString(R.string.videocall_remote_video_off)); 957 // The text needs to be updated and hidden after enough delay in order to be announced by 958 // talkback. 959 remoteVideoOff.setText( 960 wasRemoteVideoOff 961 ? R.string.videocall_remote_video_on 962 : R.string.videocall_remotely_resumed); 963 remoteVideoOff.postDelayed( 964 new Runnable() { 965 @Override 966 public void run() { 967 remoteVideoOff.setVisibility(View.GONE); 968 } 969 }, 970 VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS); 971 } else { 972 remoteVideoOff.setText( 973 isRemotelyHeld ? R.string.videocall_remotely_held : R.string.videocall_remote_video_off); 974 remoteVideoOff.setVisibility(View.VISIBLE); 975 } 976 } 977 updateOverlayBackground()978 private void updateOverlayBackground() { 979 if (isInGreenScreenMode) { 980 // We want to darken the preview view to make text and buttons readable. The fullscreen 981 // background is below the preview view so use the green screen background instead. 982 animateSetVisibility(greenScreenBackgroundView, View.VISIBLE); 983 animateSetVisibility(fullscreenBackgroundView, View.GONE); 984 } else if (!isInFullscreenMode) { 985 // We want to darken the remote view to make text and buttons readable. The green screen 986 // background is above the preview view so it would darken the preview too. Use the fullscreen 987 // background instead. 988 animateSetVisibility(greenScreenBackgroundView, View.GONE); 989 animateSetVisibility(fullscreenBackgroundView, View.VISIBLE); 990 } else { 991 animateSetVisibility(greenScreenBackgroundView, View.GONE); 992 animateSetVisibility(fullscreenBackgroundView, View.GONE); 993 } 994 } 995 updateMutePreviewOverlayVisibility()996 private void updateMutePreviewOverlayVisibility() { 997 // Normally the mute overlay shows on the bottom right of the preview bubble. In green screen 998 // mode the preview is fullscreen so there's no where to anchor it. 999 mutePreviewOverlay.setVisibility( 1000 muteButton.isChecked() && !isInGreenScreenMode ? View.VISIBLE : View.GONE); 1001 } 1002 animateSetVisibility(final View view, final int visibility)1003 private static void animateSetVisibility(final View view, final int visibility) { 1004 if (view.getVisibility() == visibility) { 1005 return; 1006 } 1007 1008 int startAlpha; 1009 int endAlpha; 1010 if (visibility == View.GONE) { 1011 startAlpha = 1; 1012 endAlpha = 0; 1013 } else if (visibility == View.VISIBLE) { 1014 startAlpha = 0; 1015 endAlpha = 1; 1016 } else { 1017 Assert.fail(); 1018 return; 1019 } 1020 1021 view.setAlpha(startAlpha); 1022 view.setVisibility(View.VISIBLE); 1023 view.animate() 1024 .alpha(endAlpha) 1025 .withEndAction( 1026 new Runnable() { 1027 @Override 1028 public void run() { 1029 view.setVisibility(visibility); 1030 } 1031 }) 1032 .start(); 1033 } 1034 1035 @Override onSystemUiVisibilityChange(int visibility)1036 public void onSystemUiVisibilityChange(int visibility) { 1037 boolean navBarVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; 1038 videoCallScreenDelegate.onSystemUiVisibilityChange(navBarVisible); 1039 if (navBarVisible) { 1040 updateFullscreenAndGreenScreenMode( 1041 false /* shouldShowFullscreen */, false /* shouldShowGreenScreen */); 1042 } else { 1043 updateFullscreenAndGreenScreenMode( 1044 true /* shouldShowFullscreen */, false /* shouldShowGreenScreen */); 1045 } 1046 } 1047 checkCameraPermission()1048 private void checkCameraPermission() { 1049 // Checks if user has consent of camera permission and the permission is granted. 1050 // If camera permission is revoked, shows system permission dialog. 1051 // If camera permission is granted but user doesn't have consent of camera permission 1052 // (which means it's first time making video call), shows custom dialog instead. This 1053 // will only be shown to user once. 1054 if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) { 1055 videoCallScreenDelegate.onCameraPermissionDialogShown(); 1056 if (!VideoUtils.hasCameraPermission(getContext())) { 1057 requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); 1058 } else { 1059 PermissionsUtil.showCameraPermissionToast(getContext()); 1060 videoCallScreenDelegate.onCameraPermissionGranted(); 1061 } 1062 } 1063 } 1064 } 1065