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.content.res.Resources; 23 import android.graphics.Bitmap; 24 import android.graphics.Outline; 25 import android.graphics.Point; 26 import android.graphics.drawable.Animatable; 27 import android.os.Bundle; 28 import android.os.SystemClock; 29 import android.renderscript.Allocation; 30 import android.renderscript.Element; 31 import android.renderscript.RenderScript; 32 import android.renderscript.ScriptIntrinsicBlur; 33 import android.support.annotation.ColorInt; 34 import android.support.annotation.NonNull; 35 import android.support.annotation.Nullable; 36 import android.support.annotation.VisibleForTesting; 37 import android.support.v4.app.Fragment; 38 import android.support.v4.app.FragmentTransaction; 39 import android.support.v4.view.animation.FastOutLinearInInterpolator; 40 import android.support.v4.view.animation.LinearOutSlowInInterpolator; 41 import android.telecom.CallAudioState; 42 import android.text.TextUtils; 43 import android.view.LayoutInflater; 44 import android.view.Surface; 45 import android.view.TextureView; 46 import android.view.View; 47 import android.view.View.OnClickListener; 48 import android.view.View.OnLayoutChangeListener; 49 import android.view.View.OnSystemUiVisibilityChangeListener; 50 import android.view.ViewGroup; 51 import android.view.ViewGroup.MarginLayoutParams; 52 import android.view.ViewOutlineProvider; 53 import android.view.accessibility.AccessibilityEvent; 54 import android.view.animation.AccelerateDecelerateInterpolator; 55 import android.view.animation.Interpolator; 56 import android.widget.ImageButton; 57 import android.widget.ImageView; 58 import android.widget.RelativeLayout; 59 import android.widget.TextView; 60 import com.android.dialer.common.Assert; 61 import com.android.dialer.common.FragmentUtils; 62 import com.android.dialer.common.LogUtil; 63 import com.android.dialer.compat.ActivityCompat; 64 import com.android.dialer.util.PermissionsUtil; 65 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment; 66 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter; 67 import com.android.incallui.contactgrid.ContactGridManager; 68 import com.android.incallui.hold.OnHoldFragment; 69 import com.android.incallui.incall.protocol.InCallButtonIds; 70 import com.android.incallui.incall.protocol.InCallButtonIdsExtension; 71 import com.android.incallui.incall.protocol.InCallButtonUi; 72 import com.android.incallui.incall.protocol.InCallButtonUiDelegate; 73 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory; 74 import com.android.incallui.incall.protocol.InCallScreen; 75 import com.android.incallui.incall.protocol.InCallScreenDelegate; 76 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory; 77 import com.android.incallui.incall.protocol.PrimaryCallState; 78 import com.android.incallui.incall.protocol.PrimaryInfo; 79 import com.android.incallui.incall.protocol.SecondaryInfo; 80 import com.android.incallui.video.impl.CheckableImageButton.OnCheckedChangeListener; 81 import com.android.incallui.video.protocol.VideoCallScreen; 82 import com.android.incallui.video.protocol.VideoCallScreenDelegate; 83 import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory; 84 import com.android.incallui.videosurface.bindings.VideoSurfaceBindings; 85 import com.android.incallui.videosurface.protocol.VideoSurfaceTexture; 86 import com.android.incallui.videotech.utils.VideoUtils; 87 88 /** Contains UI elements for a video call. */ 89 90 public class VideoCallFragment extends Fragment 91 implements InCallScreen, 92 InCallButtonUi, 93 VideoCallScreen, 94 OnClickListener, 95 OnCheckedChangeListener, 96 AudioRouteSelectorPresenter, 97 OnSystemUiVisibilityChangeListener { 98 99 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 100 static final String ARG_CALL_ID = "call_id"; 101 102 @VisibleForTesting static final float BLUR_PREVIEW_RADIUS = 16.0f; 103 @VisibleForTesting static final float BLUR_PREVIEW_SCALE_FACTOR = 1.0f; 104 private static final float BLUR_REMOTE_RADIUS = 25.0f; 105 private static final float BLUR_REMOTE_SCALE_FACTOR = 0.25f; 106 private static final float ASPECT_RATIO_MATCH_THRESHOLD = 0.2f; 107 108 private static final int CAMERA_PERMISSION_REQUEST_CODE = 1; 109 private static final long CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS = 2000L; 110 private static final long VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS = 2000L; 111 112 private final ViewOutlineProvider circleOutlineProvider = 113 new ViewOutlineProvider() { 114 @Override 115 public void getOutline(View view, Outline outline) { 116 int x = view.getWidth() / 2; 117 int y = view.getHeight() / 2; 118 int radius = Math.min(x, y); 119 outline.setOval(x - radius, y - radius, x + radius, y + radius); 120 } 121 }; 122 123 private InCallScreenDelegate inCallScreenDelegate; 124 private VideoCallScreenDelegate videoCallScreenDelegate; 125 private InCallButtonUiDelegate inCallButtonUiDelegate; 126 private View endCallButton; 127 private CheckableImageButton speakerButton; 128 private SpeakerButtonController speakerButtonController; 129 private CheckableImageButton muteButton; 130 private CheckableImageButton cameraOffButton; 131 private ImageButton swapCameraButton; 132 private View switchOnHoldButton; 133 private View onHoldContainer; 134 private SwitchOnHoldCallController switchOnHoldCallController; 135 private TextView remoteVideoOff; 136 private ImageView remoteOffBlurredImageView; 137 private View mutePreviewOverlay; 138 private View previewOffOverlay; 139 private ImageView previewOffBlurredImageView; 140 private View controls; 141 private View controlsContainer; 142 private TextureView previewTextureView; 143 private TextureView remoteTextureView; 144 private View greenScreenBackgroundView; 145 private View fullscreenBackgroundView; 146 private boolean shouldShowRemote; 147 private boolean shouldShowPreview; 148 private boolean isInFullscreenMode; 149 private boolean isInGreenScreenMode; 150 private boolean hasInitializedScreenModes; 151 private boolean isRemotelyHeld; 152 private ContactGridManager contactGridManager; 153 private SecondaryInfo savedSecondaryInfo; 154 private final Runnable cameraPermissionDialogRunnable = 155 new Runnable() { 156 @Override 157 public void run() { 158 if (videoCallScreenDelegate.shouldShowCameraPermissionToast()) { 159 LogUtil.i("VideoCallFragment.cameraPermissionDialogRunnable", "showing dialog"); 160 checkCameraPermission(); 161 } 162 } 163 }; 164 165 public static VideoCallFragment newInstance(String callId) { 166 Bundle bundle = new Bundle(); 167 bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId)); 168 169 VideoCallFragment instance = new VideoCallFragment(); 170 instance.setArguments(bundle); 171 return instance; 172 } 173 174 @Override 175 public void onCreate(@Nullable Bundle savedInstanceState) { 176 super.onCreate(savedInstanceState); 177 LogUtil.i("VideoCallFragment.onCreate", null); 178 179 inCallButtonUiDelegate = 180 FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class) 181 .newInCallButtonUiDelegate(); 182 if (savedInstanceState != null) { 183 inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState); 184 } 185 } 186 187 @Override 188 public void onRequestPermissionsResult( 189 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 190 if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { 191 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 192 LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission granted."); 193 videoCallScreenDelegate.onCameraPermissionGranted(); 194 } else { 195 LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission denied."); 196 } 197 } 198 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 199 } 200 201 @Nullable 202 @Override 203 public View onCreateView( 204 LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) { 205 LogUtil.i("VideoCallFragment.onCreateView", null); 206 207 View view = 208 layoutInflater.inflate( 209 isLandscape() ? R.layout.frag_videocall_land : R.layout.frag_videocall, 210 viewGroup, 211 false); 212 contactGridManager = 213 new ContactGridManager(view, null /* no avatar */, 0, false /* showAnonymousAvatar */); 214 215 controls = view.findViewById(R.id.videocall_video_controls); 216 controls.setVisibility( 217 ActivityCompat.isInMultiWindowMode(getActivity()) ? View.GONE : View.VISIBLE); 218 controlsContainer = view.findViewById(R.id.videocall_video_controls_container); 219 speakerButton = (CheckableImageButton) view.findViewById(R.id.videocall_speaker_button); 220 muteButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_button); 221 muteButton.setOnCheckedChangeListener(this); 222 mutePreviewOverlay = view.findViewById(R.id.videocall_video_preview_mute_overlay); 223 cameraOffButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_video); 224 cameraOffButton.setOnCheckedChangeListener(this); 225 previewOffOverlay = view.findViewById(R.id.videocall_video_preview_off_overlay); 226 previewOffBlurredImageView = 227 (ImageView) view.findViewById(R.id.videocall_preview_off_blurred_image_view); 228 swapCameraButton = (ImageButton) view.findViewById(R.id.videocall_switch_video); 229 swapCameraButton.setOnClickListener(this); 230 view.findViewById(R.id.videocall_switch_controls) 231 .setVisibility( 232 ActivityCompat.isInMultiWindowMode(getActivity()) ? View.GONE : View.VISIBLE); 233 switchOnHoldButton = view.findViewById(R.id.videocall_switch_on_hold); 234 onHoldContainer = view.findViewById(R.id.videocall_on_hold_banner); 235 remoteVideoOff = (TextView) view.findViewById(R.id.videocall_remote_video_off); 236 remoteVideoOff.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 237 remoteOffBlurredImageView = 238 (ImageView) view.findViewById(R.id.videocall_remote_off_blurred_image_view); 239 endCallButton = view.findViewById(R.id.videocall_end_call); 240 endCallButton.setOnClickListener(this); 241 previewTextureView = (TextureView) view.findViewById(R.id.videocall_video_preview); 242 previewTextureView.setClipToOutline(true); 243 previewOffOverlay.setOnClickListener( 244 new OnClickListener() { 245 @Override 246 public void onClick(View v) { 247 checkCameraPermission(); 248 } 249 }); 250 remoteTextureView = (TextureView) view.findViewById(R.id.videocall_video_remote); 251 greenScreenBackgroundView = view.findViewById(R.id.videocall_green_screen_background); 252 fullscreenBackgroundView = view.findViewById(R.id.videocall_fullscreen_background); 253 254 remoteTextureView.addOnLayoutChangeListener( 255 new OnLayoutChangeListener() { 256 @Override 257 public void onLayoutChange( 258 View v, 259 int left, 260 int top, 261 int right, 262 int bottom, 263 int oldLeft, 264 int oldTop, 265 int oldRight, 266 int oldBottom) { 267 LogUtil.i("VideoCallFragment.onLayoutChange", "remoteTextureView layout changed"); 268 updateRemoteVideoScaling(); 269 updateRemoteOffView(); 270 } 271 }); 272 273 previewTextureView.addOnLayoutChangeListener( 274 new OnLayoutChangeListener() { 275 @Override 276 public void onLayoutChange( 277 View v, 278 int left, 279 int top, 280 int right, 281 int bottom, 282 int oldLeft, 283 int oldTop, 284 int oldRight, 285 int oldBottom) { 286 LogUtil.i("VideoCallFragment.onLayoutChange", "previewTextureView layout changed"); 287 updatePreviewVideoScaling(); 288 updatePreviewOffView(); 289 } 290 }); 291 return view; 292 } 293 294 @Override 295 public void onViewCreated(View view, @Nullable Bundle bundle) { 296 super.onViewCreated(view, bundle); 297 LogUtil.i("VideoCallFragment.onViewCreated", null); 298 299 inCallScreenDelegate = 300 FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class) 301 .newInCallScreenDelegate(); 302 videoCallScreenDelegate = 303 FragmentUtils.getParentUnsafe(this, VideoCallScreenDelegateFactory.class) 304 .newVideoCallScreenDelegate(this); 305 306 speakerButtonController = 307 new SpeakerButtonController(speakerButton, inCallButtonUiDelegate, videoCallScreenDelegate); 308 switchOnHoldCallController = 309 new SwitchOnHoldCallController( 310 switchOnHoldButton, onHoldContainer, inCallScreenDelegate, videoCallScreenDelegate); 311 312 videoCallScreenDelegate.initVideoCallScreenDelegate(getContext(), this); 313 314 inCallScreenDelegate.onInCallScreenDelegateInit(this); 315 inCallScreenDelegate.onInCallScreenReady(); 316 inCallButtonUiDelegate.onInCallButtonUiReady(this); 317 318 view.setOnSystemUiVisibilityChangeListener(this); 319 } 320 321 @Override 322 public void onSaveInstanceState(Bundle outState) { 323 super.onSaveInstanceState(outState); 324 inCallButtonUiDelegate.onSaveInstanceState(outState); 325 } 326 327 @Override 328 public void onDestroyView() { 329 super.onDestroyView(); 330 LogUtil.i("VideoCallFragment.onDestroyView", null); 331 inCallButtonUiDelegate.onInCallButtonUiUnready(); 332 inCallScreenDelegate.onInCallScreenUnready(); 333 } 334 335 @Override 336 public void onAttach(Context context) { 337 super.onAttach(context); 338 if (savedSecondaryInfo != null) { 339 setSecondary(savedSecondaryInfo); 340 } 341 } 342 343 @Override 344 public void onStart() { 345 super.onStart(); 346 LogUtil.i("VideoCallFragment.onStart", null); 347 onVideoScreenStart(); 348 } 349 350 @Override 351 public void onVideoScreenStart() { 352 inCallButtonUiDelegate.refreshMuteState(); 353 videoCallScreenDelegate.onVideoCallScreenUiReady(); 354 getView().postDelayed(cameraPermissionDialogRunnable, CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS); 355 } 356 357 @Override 358 public void onResume() { 359 super.onResume(); 360 LogUtil.i("VideoCallFragment.onResume", null); 361 inCallScreenDelegate.onInCallScreenResumed(); 362 } 363 364 @Override 365 public void onPause() { 366 super.onPause(); 367 LogUtil.i("VideoCallFragment.onPause", null); 368 inCallScreenDelegate.onInCallScreenPaused(); 369 } 370 371 @Override 372 public void onStop() { 373 super.onStop(); 374 LogUtil.i("VideoCallFragment.onStop", null); 375 onVideoScreenStop(); 376 } 377 378 @Override 379 public void onVideoScreenStop() { 380 getView().removeCallbacks(cameraPermissionDialogRunnable); 381 videoCallScreenDelegate.onVideoCallScreenUiUnready(); 382 } 383 384 private void exitFullscreenMode() { 385 LogUtil.i("VideoCallFragment.exitFullscreenMode", null); 386 387 if (!getView().isAttachedToWindow()) { 388 LogUtil.i("VideoCallFragment.exitFullscreenMode", "not attached"); 389 return; 390 } 391 392 showSystemUI(); 393 394 LinearOutSlowInInterpolator linearOutSlowInInterpolator = new LinearOutSlowInInterpolator(); 395 396 // Animate the controls to the shown state. 397 controls 398 .animate() 399 .translationX(0) 400 .translationY(0) 401 .setInterpolator(linearOutSlowInInterpolator) 402 .alpha(1) 403 .start(); 404 405 // Animate onHold to the shown state. 406 switchOnHoldButton 407 .animate() 408 .translationX(0) 409 .translationY(0) 410 .setInterpolator(linearOutSlowInInterpolator) 411 .alpha(1) 412 .withStartAction( 413 new Runnable() { 414 @Override 415 public void run() { 416 switchOnHoldCallController.setOnScreen(); 417 } 418 }); 419 420 View contactGridView = contactGridManager.getContainerView(); 421 // Animate contact grid to the shown state. 422 contactGridView 423 .animate() 424 .translationX(0) 425 .translationY(0) 426 .setInterpolator(linearOutSlowInInterpolator) 427 .alpha(1) 428 .withStartAction( 429 new Runnable() { 430 @Override 431 public void run() { 432 contactGridManager.show(); 433 } 434 }); 435 436 endCallButton 437 .animate() 438 .translationX(0) 439 .translationY(0) 440 .setInterpolator(linearOutSlowInInterpolator) 441 .alpha(1) 442 .withStartAction( 443 new Runnable() { 444 @Override 445 public void run() { 446 endCallButton.setVisibility(View.VISIBLE); 447 } 448 }) 449 .start(); 450 451 // Animate all the preview controls up to make room for the navigation bar. 452 // In green screen mode we don't need this because the preview takes up the whole screen and has 453 // a fixed position. 454 if (!isInGreenScreenMode) { 455 Point previewOffsetStartShown = getPreviewOffsetStartShown(); 456 for (View view : getAllPreviewRelatedViews()) { 457 // Animate up with the preview offset above the navigation bar. 458 view.animate() 459 .translationX(previewOffsetStartShown.x) 460 .translationY(previewOffsetStartShown.y) 461 .setInterpolator(new AccelerateDecelerateInterpolator()) 462 .start(); 463 } 464 } 465 466 updateOverlayBackground(); 467 } 468 469 private void showSystemUI() { 470 View view = getView(); 471 if (view != null) { 472 // Code is more expressive with all flags present, even though some may be combined 473 // noinspection PointlessBitwiseExpression 474 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 475 } 476 } 477 478 /** Set view flags to hide the system UI. System UI will return on any touch event */ 479 private void hideSystemUI() { 480 View view = getView(); 481 if (view != null) { 482 view.setSystemUiVisibility( 483 View.SYSTEM_UI_FLAG_FULLSCREEN 484 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 485 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 486 } 487 } 488 489 private Point getControlsOffsetEndHidden(View controls) { 490 if (isLandscape()) { 491 return new Point(0, getOffsetBottom(controls)); 492 } else { 493 return new Point(getOffsetStart(controls), 0); 494 } 495 } 496 497 private Point getSwitchOnHoldOffsetEndHidden(View swapCallButton) { 498 if (isLandscape()) { 499 return new Point(0, getOffsetTop(swapCallButton)); 500 } else { 501 return new Point(getOffsetEnd(swapCallButton), 0); 502 } 503 } 504 505 private Point getContactGridOffsetEndHidden(View view) { 506 return new Point(0, getOffsetTop(view)); 507 } 508 509 private Point getEndCallOffsetEndHidden(View endCallButton) { 510 if (isLandscape()) { 511 return new Point(getOffsetEnd(endCallButton), 0); 512 } else { 513 return new Point(0, ((MarginLayoutParams) endCallButton.getLayoutParams()).bottomMargin); 514 } 515 } 516 517 private Point getPreviewOffsetStartShown() { 518 // No insets in multiwindow mode, and rootWindowInsets will get the display's insets. 519 if (ActivityCompat.isInMultiWindowMode(getActivity())) { 520 return new Point(); 521 } 522 if (isLandscape()) { 523 int stableInsetEnd = 524 getView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL 525 ? getView().getRootWindowInsets().getStableInsetLeft() 526 : -getView().getRootWindowInsets().getStableInsetRight(); 527 return new Point(stableInsetEnd, 0); 528 } else { 529 return new Point(0, -getView().getRootWindowInsets().getStableInsetBottom()); 530 } 531 } 532 533 private View[] getAllPreviewRelatedViews() { 534 return new View[] { 535 previewTextureView, previewOffOverlay, previewOffBlurredImageView, mutePreviewOverlay, 536 }; 537 } 538 539 private int getOffsetTop(View view) { 540 return -(view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).topMargin); 541 } 542 543 private int getOffsetBottom(View view) { 544 return view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).bottomMargin; 545 } 546 547 private int getOffsetStart(View view) { 548 int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginStart(); 549 if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 550 offset = -offset; 551 } 552 return -offset; 553 } 554 555 private int getOffsetEnd(View view) { 556 int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginEnd(); 557 if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 558 offset = -offset; 559 } 560 return offset; 561 } 562 563 private void enterFullscreenMode() { 564 LogUtil.i("VideoCallFragment.enterFullscreenMode", null); 565 566 hideSystemUI(); 567 568 Interpolator fastOutLinearInInterpolator = new FastOutLinearInInterpolator(); 569 570 // Animate controls to the hidden state. 571 Point offset = getControlsOffsetEndHidden(controls); 572 controls 573 .animate() 574 .translationX(offset.x) 575 .translationY(offset.y) 576 .setInterpolator(fastOutLinearInInterpolator) 577 .alpha(0) 578 .start(); 579 580 // Animate onHold to the hidden state. 581 offset = getSwitchOnHoldOffsetEndHidden(switchOnHoldButton); 582 switchOnHoldButton 583 .animate() 584 .translationX(offset.x) 585 .translationY(offset.y) 586 .setInterpolator(fastOutLinearInInterpolator) 587 .alpha(0); 588 589 View contactGridView = contactGridManager.getContainerView(); 590 // Animate contact grid to the hidden state. 591 offset = getContactGridOffsetEndHidden(contactGridView); 592 contactGridView 593 .animate() 594 .translationX(offset.x) 595 .translationY(offset.y) 596 .setInterpolator(fastOutLinearInInterpolator) 597 .alpha(0); 598 599 offset = getEndCallOffsetEndHidden(endCallButton); 600 // Use a fast out interpolator to quickly fade out the button. This is important because the 601 // button can't draw under the navigation bar which means that it'll look weird if it just 602 // abruptly disappears when it reaches the edge of the naivgation bar. 603 endCallButton 604 .animate() 605 .translationX(offset.x) 606 .translationY(offset.y) 607 .setInterpolator(fastOutLinearInInterpolator) 608 .alpha(0) 609 .withEndAction( 610 new Runnable() { 611 @Override 612 public void run() { 613 endCallButton.setVisibility(View.INVISIBLE); 614 } 615 }) 616 .setInterpolator(new FastOutLinearInInterpolator()) 617 .start(); 618 619 // Animate all the preview controls down now that the navigation bar is hidden. 620 // In green screen mode we don't need this because the preview takes up the whole screen and has 621 // a fixed position. 622 if (!isInGreenScreenMode) { 623 for (View view : getAllPreviewRelatedViews()) { 624 // Animate down with the navigation bar hidden. 625 view.animate() 626 .translationX(0) 627 .translationY(0) 628 .setInterpolator(new AccelerateDecelerateInterpolator()) 629 .start(); 630 } 631 } 632 updateOverlayBackground(); 633 } 634 635 @Override 636 public void onClick(View v) { 637 if (v == endCallButton) { 638 LogUtil.i("VideoCallFragment.onClick", "end call button clicked"); 639 inCallButtonUiDelegate.onEndCallClicked(); 640 videoCallScreenDelegate.resetAutoFullscreenTimer(); 641 } else if (v == swapCameraButton) { 642 if (swapCameraButton.getDrawable() instanceof Animatable) { 643 ((Animatable) swapCameraButton.getDrawable()).start(); 644 } 645 inCallButtonUiDelegate.toggleCameraClicked(); 646 videoCallScreenDelegate.resetAutoFullscreenTimer(); 647 } 648 } 649 650 @Override 651 public void onCheckedChanged(CheckableImageButton button, boolean isChecked) { 652 if (button == cameraOffButton) { 653 if (!isChecked && !VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) { 654 LogUtil.i("VideoCallFragment.onCheckedChanged", "show camera permission dialog"); 655 checkCameraPermission(); 656 } else { 657 inCallButtonUiDelegate.pauseVideoClicked(isChecked); 658 videoCallScreenDelegate.resetAutoFullscreenTimer(); 659 } 660 } else if (button == muteButton) { 661 inCallButtonUiDelegate.muteClicked(isChecked, true /* clickedByUser */); 662 videoCallScreenDelegate.resetAutoFullscreenTimer(); 663 } 664 } 665 666 @Override 667 public void showVideoViews( 668 boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld) { 669 LogUtil.i( 670 "VideoCallFragment.showVideoViews", 671 "showPreview: %b, shouldShowRemote: %b", 672 shouldShowPreview, 673 shouldShowRemote); 674 675 videoCallScreenDelegate.getLocalVideoSurfaceTexture().attachToTextureView(previewTextureView); 676 videoCallScreenDelegate.getRemoteVideoSurfaceTexture().attachToTextureView(remoteTextureView); 677 678 this.isRemotelyHeld = isRemotelyHeld; 679 if (this.shouldShowRemote != shouldShowRemote) { 680 this.shouldShowRemote = shouldShowRemote; 681 updateRemoteOffView(); 682 } 683 if (this.shouldShowPreview != shouldShowPreview) { 684 this.shouldShowPreview = shouldShowPreview; 685 updatePreviewOffView(); 686 } 687 } 688 689 @Override 690 public void onLocalVideoDimensionsChanged() { 691 LogUtil.i("VideoCallFragment.onLocalVideoDimensionsChanged", null); 692 updatePreviewVideoScaling(); 693 } 694 695 @Override 696 public void onLocalVideoOrientationChanged() { 697 LogUtil.i("VideoCallFragment.onLocalVideoOrientationChanged", null); 698 updatePreviewVideoScaling(); 699 } 700 701 /** Called when the remote video's dimensions change. */ 702 @Override 703 public void onRemoteVideoDimensionsChanged() { 704 LogUtil.i("VideoCallFragment.onRemoteVideoDimensionsChanged", null); 705 updateRemoteVideoScaling(); 706 } 707 708 @Override 709 public void updateFullscreenAndGreenScreenMode( 710 boolean shouldShowFullscreen, boolean shouldShowGreenScreen) { 711 LogUtil.i( 712 "VideoCallFragment.updateFullscreenAndGreenScreenMode", 713 "shouldShowFullscreen: %b, shouldShowGreenScreen: %b", 714 shouldShowFullscreen, 715 shouldShowGreenScreen); 716 717 if (getActivity() == null) { 718 LogUtil.i("VideoCallFragment.updateFullscreenAndGreenScreenMode", "not attached to activity"); 719 return; 720 } 721 722 // Check if anything is actually going to change. The first time this function is called we 723 // force a change by checking the hasInitializedScreenModes flag. We also force both fullscreen 724 // and green screen modes to update even if only one has changed. That's because they both 725 // depend on each other. 726 if (hasInitializedScreenModes 727 && shouldShowGreenScreen == isInGreenScreenMode 728 && shouldShowFullscreen == isInFullscreenMode) { 729 LogUtil.i( 730 "VideoCallFragment.updateFullscreenAndGreenScreenMode", "no change to screen modes"); 731 return; 732 } 733 hasInitializedScreenModes = true; 734 isInGreenScreenMode = shouldShowGreenScreen; 735 isInFullscreenMode = shouldShowFullscreen; 736 737 if (getView().isAttachedToWindow() && !ActivityCompat.isInMultiWindowMode(getActivity())) { 738 controlsContainer.onApplyWindowInsets(getView().getRootWindowInsets()); 739 } 740 if (shouldShowGreenScreen) { 741 enterGreenScreenMode(); 742 } else { 743 exitGreenScreenMode(); 744 } 745 if (shouldShowFullscreen) { 746 enterFullscreenMode(); 747 } else { 748 exitFullscreenMode(); 749 } 750 751 OnHoldFragment onHoldFragment = 752 ((OnHoldFragment) 753 getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner)); 754 if (onHoldFragment != null) { 755 onHoldFragment.setPadTopInset(!isInFullscreenMode); 756 } 757 } 758 759 @Override 760 public Fragment getVideoCallScreenFragment() { 761 return this; 762 } 763 764 @Override 765 @NonNull 766 public String getCallId() { 767 return Assert.isNotNull(getArguments().getString(ARG_CALL_ID)); 768 } 769 770 @Override 771 public void showButton(@InCallButtonIds int buttonId, boolean show) { 772 LogUtil.v( 773 "VideoCallFragment.showButton", 774 "buttonId: %s, show: %b", 775 InCallButtonIdsExtension.toString(buttonId), 776 show); 777 if (buttonId == InCallButtonIds.BUTTON_AUDIO) { 778 speakerButtonController.setEnabled(show); 779 } else if (buttonId == InCallButtonIds.BUTTON_MUTE) { 780 muteButton.setEnabled(show); 781 } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) { 782 cameraOffButton.setEnabled(show); 783 } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { 784 switchOnHoldCallController.setVisible(show); 785 } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_CAMERA) { 786 swapCameraButton.setEnabled(show); 787 } 788 } 789 790 @Override 791 public void enableButton(@InCallButtonIds int buttonId, boolean enable) { 792 LogUtil.v( 793 "VideoCallFragment.setEnabled", 794 "buttonId: %s, enable: %b", 795 InCallButtonIdsExtension.toString(buttonId), 796 enable); 797 if (buttonId == InCallButtonIds.BUTTON_AUDIO) { 798 speakerButtonController.setEnabled(enable); 799 } else if (buttonId == InCallButtonIds.BUTTON_MUTE) { 800 muteButton.setEnabled(enable); 801 } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) { 802 cameraOffButton.setEnabled(enable); 803 } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) { 804 switchOnHoldCallController.setEnabled(enable); 805 } 806 } 807 808 @Override 809 public void setEnabled(boolean enabled) { 810 LogUtil.v("VideoCallFragment.setEnabled", "enabled: " + enabled); 811 speakerButtonController.setEnabled(enabled); 812 muteButton.setEnabled(enabled); 813 cameraOffButton.setEnabled(enabled); 814 switchOnHoldCallController.setEnabled(enabled); 815 } 816 817 @Override 818 public void setHold(boolean value) { 819 LogUtil.i("VideoCallFragment.setHold", "value: " + value); 820 } 821 822 @Override 823 public void setCameraSwitched(boolean isBackFacingCamera) { 824 LogUtil.i("VideoCallFragment.setCameraSwitched", "isBackFacingCamera: " + isBackFacingCamera); 825 } 826 827 @Override 828 public void setVideoPaused(boolean isPaused) { 829 LogUtil.i("VideoCallFragment.setVideoPaused", "isPaused: " + isPaused); 830 cameraOffButton.setChecked(isPaused); 831 } 832 833 @Override 834 public void setAudioState(CallAudioState audioState) { 835 LogUtil.i("VideoCallFragment.setAudioState", "audioState: " + audioState); 836 speakerButtonController.setAudioState(audioState); 837 muteButton.setChecked(audioState.isMuted()); 838 updateMutePreviewOverlayVisibility(); 839 } 840 841 @Override 842 public void updateButtonStates() { 843 LogUtil.i("VideoCallFragment.updateButtonState", null); 844 speakerButtonController.updateButtonState(); 845 switchOnHoldCallController.updateButtonState(); 846 } 847 848 @Override 849 public void updateInCallButtonUiColors(@ColorInt int color) {} 850 851 @Override 852 public Fragment getInCallButtonUiFragment() { 853 return this; 854 } 855 856 @Override 857 public void showAudioRouteSelector() { 858 LogUtil.i("VideoCallFragment.showAudioRouteSelector", null); 859 AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState()) 860 .show(getChildFragmentManager(), null); 861 } 862 863 @Override 864 public void onAudioRouteSelected(int audioRoute) { 865 LogUtil.i("VideoCallFragment.onAudioRouteSelected", "audioRoute: " + audioRoute); 866 inCallButtonUiDelegate.setAudioRoute(audioRoute); 867 } 868 869 @Override 870 public void onAudioRouteSelectorDismiss() {} 871 872 @Override 873 public void setPrimary(@NonNull PrimaryInfo primaryInfo) { 874 LogUtil.i("VideoCallFragment.setPrimary", primaryInfo.toString()); 875 contactGridManager.setPrimary(primaryInfo); 876 } 877 878 @Override 879 public void setSecondary(@NonNull SecondaryInfo secondaryInfo) { 880 LogUtil.i("VideoCallFragment.setSecondary", secondaryInfo.toString()); 881 if (!isAdded()) { 882 savedSecondaryInfo = secondaryInfo; 883 return; 884 } 885 savedSecondaryInfo = null; 886 switchOnHoldCallController.setSecondaryInfo(secondaryInfo); 887 updateButtonStates(); 888 FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); 889 Fragment oldBanner = getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner); 890 if (secondaryInfo.shouldShow()) { 891 OnHoldFragment onHoldFragment = OnHoldFragment.newInstance(secondaryInfo); 892 onHoldFragment.setPadTopInset(!isInFullscreenMode); 893 transaction.replace(R.id.videocall_on_hold_banner, onHoldFragment); 894 } else { 895 if (oldBanner != null) { 896 transaction.remove(oldBanner); 897 } 898 } 899 transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top); 900 transaction.commitAllowingStateLoss(); 901 } 902 903 @Override 904 public void setCallState(@NonNull PrimaryCallState primaryCallState) { 905 LogUtil.i("VideoCallFragment.setCallState", primaryCallState.toString()); 906 contactGridManager.setCallState(primaryCallState); 907 } 908 909 @Override 910 public void setEndCallButtonEnabled(boolean enabled, boolean animate) { 911 LogUtil.i("VideoCallFragment.setEndCallButtonEnabled", "enabled: " + enabled); 912 } 913 914 @Override 915 public void showManageConferenceCallButton(boolean visible) { 916 LogUtil.i("VideoCallFragment.showManageConferenceCallButton", "visible: " + visible); 917 } 918 919 @Override 920 public boolean isManageConferenceVisible() { 921 LogUtil.i("VideoCallFragment.isManageConferenceVisible", null); 922 return false; 923 } 924 925 @Override 926 public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 927 contactGridManager.dispatchPopulateAccessibilityEvent(event); 928 } 929 930 @Override 931 public void showNoteSentToast() { 932 LogUtil.i("VideoCallFragment.showNoteSentToast", null); 933 } 934 935 @Override 936 public void updateInCallScreenColors() { 937 LogUtil.i("VideoCallFragment.updateColors", null); 938 } 939 940 @Override 941 public void onInCallScreenDialpadVisibilityChange(boolean isShowing) { 942 LogUtil.i("VideoCallFragment.onInCallScreenDialpadVisibilityChange", null); 943 } 944 945 @Override 946 public int getAnswerAndDialpadContainerResourceId() { 947 return 0; 948 } 949 950 @Override 951 public Fragment getInCallScreenFragment() { 952 return this; 953 } 954 955 @Override 956 public boolean isShowingLocationUi() { 957 return false; 958 } 959 960 @Override 961 public void showLocationUi(Fragment locationUi) { 962 LogUtil.e("VideoCallFragment.showLocationUi", "Emergency video calling not supported"); 963 // Do nothing 964 } 965 966 private void updatePreviewVideoScaling() { 967 if (previewTextureView.getWidth() == 0 || previewTextureView.getHeight() == 0) { 968 LogUtil.i("VideoCallFragment.updatePreviewVideoScaling", "view layout hasn't finished yet"); 969 return; 970 } 971 VideoSurfaceTexture localVideoSurfaceTexture = 972 videoCallScreenDelegate.getLocalVideoSurfaceTexture(); 973 Point cameraDimensions = localVideoSurfaceTexture.getSurfaceDimensions(); 974 if (cameraDimensions == null) { 975 LogUtil.i( 976 "VideoCallFragment.updatePreviewVideoScaling", "camera dimensions haven't been set"); 977 return; 978 } 979 if (isLandscape()) { 980 VideoSurfaceBindings.scaleVideoAndFillView( 981 previewTextureView, 982 cameraDimensions.x, 983 cameraDimensions.y, 984 videoCallScreenDelegate.getDeviceOrientation()); 985 } else { 986 VideoSurfaceBindings.scaleVideoAndFillView( 987 previewTextureView, 988 cameraDimensions.y, 989 cameraDimensions.x, 990 videoCallScreenDelegate.getDeviceOrientation()); 991 } 992 } 993 994 private void updateRemoteVideoScaling() { 995 VideoSurfaceTexture remoteVideoSurfaceTexture = 996 videoCallScreenDelegate.getRemoteVideoSurfaceTexture(); 997 Point videoSize = remoteVideoSurfaceTexture.getSourceVideoDimensions(); 998 if (videoSize == null) { 999 LogUtil.i("VideoCallFragment.updateRemoteVideoScaling", "video size is null"); 1000 return; 1001 } 1002 if (remoteTextureView.getWidth() == 0 || remoteTextureView.getHeight() == 0) { 1003 LogUtil.i("VideoCallFragment.updateRemoteVideoScaling", "view layout hasn't finished yet"); 1004 return; 1005 } 1006 1007 // If the video and display aspect ratio's are close then scale video to fill display 1008 float videoAspectRatio = ((float) videoSize.x) / videoSize.y; 1009 float displayAspectRatio = 1010 ((float) remoteTextureView.getWidth()) / remoteTextureView.getHeight(); 1011 float delta = Math.abs(videoAspectRatio - displayAspectRatio); 1012 float sum = videoAspectRatio + displayAspectRatio; 1013 if (delta / sum < ASPECT_RATIO_MATCH_THRESHOLD) { 1014 VideoSurfaceBindings.scaleVideoAndFillView(remoteTextureView, videoSize.x, videoSize.y, 0); 1015 } else { 1016 VideoSurfaceBindings.scaleVideoMaintainingAspectRatio( 1017 remoteTextureView, videoSize.x, videoSize.y); 1018 } 1019 } 1020 1021 private boolean isLandscape() { 1022 // Choose orientation based on display orientation, not window orientation 1023 int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation(); 1024 return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270; 1025 } 1026 1027 private void enterGreenScreenMode() { 1028 LogUtil.i("VideoCallFragment.enterGreenScreenMode", null); 1029 RelativeLayout.LayoutParams params = 1030 new RelativeLayout.LayoutParams( 1031 RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); 1032 params.addRule(RelativeLayout.ALIGN_PARENT_START); 1033 params.addRule(RelativeLayout.ALIGN_PARENT_TOP); 1034 previewTextureView.setLayoutParams(params); 1035 previewTextureView.setOutlineProvider(null); 1036 updateOverlayBackground(); 1037 contactGridManager.setIsMiddleRowVisible(true); 1038 updateMutePreviewOverlayVisibility(); 1039 1040 previewOffBlurredImageView.setLayoutParams(params); 1041 previewOffBlurredImageView.setOutlineProvider(null); 1042 previewOffBlurredImageView.setClipToOutline(false); 1043 } 1044 1045 private void exitGreenScreenMode() { 1046 LogUtil.i("VideoCallFragment.exitGreenScreenMode", null); 1047 Resources resources = getResources(); 1048 RelativeLayout.LayoutParams params = 1049 new RelativeLayout.LayoutParams( 1050 (int) resources.getDimension(R.dimen.videocall_preview_width), 1051 (int) resources.getDimension(R.dimen.videocall_preview_height)); 1052 params.setMargins( 1053 0, 0, 0, (int) resources.getDimension(R.dimen.videocall_preview_margin_bottom)); 1054 if (isLandscape()) { 1055 params.addRule(RelativeLayout.ALIGN_PARENT_END); 1056 params.setMarginEnd((int) resources.getDimension(R.dimen.videocall_preview_margin_end)); 1057 } else { 1058 params.addRule(RelativeLayout.ALIGN_PARENT_START); 1059 params.setMarginStart((int) resources.getDimension(R.dimen.videocall_preview_margin_start)); 1060 } 1061 params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 1062 previewTextureView.setLayoutParams(params); 1063 previewTextureView.setOutlineProvider(circleOutlineProvider); 1064 updateOverlayBackground(); 1065 contactGridManager.setIsMiddleRowVisible(false); 1066 updateMutePreviewOverlayVisibility(); 1067 1068 previewOffBlurredImageView.setLayoutParams(params); 1069 previewOffBlurredImageView.setOutlineProvider(circleOutlineProvider); 1070 previewOffBlurredImageView.setClipToOutline(true); 1071 } 1072 1073 private void updatePreviewOffView() { 1074 LogUtil.enterBlock("VideoCallFragment.updatePreviewOffView"); 1075 1076 // Always hide the preview off and remote off views in green screen mode. 1077 boolean previewEnabled = isInGreenScreenMode || shouldShowPreview; 1078 previewOffOverlay.setVisibility(previewEnabled ? View.GONE : View.VISIBLE); 1079 updateBlurredImageView( 1080 previewTextureView, 1081 previewOffBlurredImageView, 1082 shouldShowPreview, 1083 BLUR_PREVIEW_RADIUS, 1084 BLUR_PREVIEW_SCALE_FACTOR); 1085 } 1086 1087 private void updateRemoteOffView() { 1088 LogUtil.enterBlock("VideoCallFragment.updateRemoteOffView"); 1089 boolean remoteEnabled = isInGreenScreenMode || shouldShowRemote; 1090 boolean isResumed = remoteEnabled && !isRemotelyHeld; 1091 if (isResumed) { 1092 boolean wasRemoteVideoOff = 1093 TextUtils.equals( 1094 remoteVideoOff.getText(), 1095 remoteVideoOff.getResources().getString(R.string.videocall_remote_video_off)); 1096 // The text needs to be updated and hidden after enough delay in order to be announced by 1097 // talkback. 1098 remoteVideoOff.setText( 1099 wasRemoteVideoOff 1100 ? R.string.videocall_remote_video_on 1101 : R.string.videocall_remotely_resumed); 1102 remoteVideoOff.postDelayed( 1103 new Runnable() { 1104 @Override 1105 public void run() { 1106 remoteVideoOff.setVisibility(View.GONE); 1107 } 1108 }, 1109 VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS); 1110 } else { 1111 remoteVideoOff.setText( 1112 isRemotelyHeld ? R.string.videocall_remotely_held : R.string.videocall_remote_video_off); 1113 remoteVideoOff.setVisibility(View.VISIBLE); 1114 } 1115 updateBlurredImageView( 1116 remoteTextureView, 1117 remoteOffBlurredImageView, 1118 shouldShowRemote, 1119 BLUR_REMOTE_RADIUS, 1120 BLUR_REMOTE_SCALE_FACTOR); 1121 } 1122 1123 @VisibleForTesting 1124 void updateBlurredImageView( 1125 TextureView textureView, 1126 ImageView blurredImageView, 1127 boolean isVideoEnabled, 1128 float blurRadius, 1129 float scaleFactor) { 1130 Context context = getContext(); 1131 1132 if (isVideoEnabled || context == null) { 1133 blurredImageView.setImageBitmap(null); 1134 blurredImageView.setVisibility(View.GONE); 1135 return; 1136 } 1137 1138 long startTimeMillis = SystemClock.elapsedRealtime(); 1139 int width = Math.round(textureView.getWidth() * scaleFactor); 1140 int height = Math.round(textureView.getHeight() * scaleFactor); 1141 1142 LogUtil.i("VideoCallFragment.updateBlurredImageView", "width: %d, height: %d", width, height); 1143 1144 // This call takes less than 10 milliseconds. 1145 Bitmap bitmap = textureView.getBitmap(width, height); 1146 1147 if (bitmap == null) { 1148 blurredImageView.setImageBitmap(null); 1149 blurredImageView.setVisibility(View.GONE); 1150 return; 1151 } 1152 1153 // TODO(mdooley): When the view is first displayed after a rotation the bitmap is empty 1154 // and thus this blur has no effect. 1155 // This call can take 100 milliseconds. 1156 blur(getContext(), bitmap, blurRadius); 1157 1158 // TODO(mdooley): Figure out why only have to apply the transform in landscape mode 1159 if (width > height) { 1160 bitmap = 1161 Bitmap.createBitmap( 1162 bitmap, 1163 0, 1164 0, 1165 bitmap.getWidth(), 1166 bitmap.getHeight(), 1167 textureView.getTransform(null), 1168 true); 1169 } 1170 1171 blurredImageView.setImageBitmap(bitmap); 1172 blurredImageView.setVisibility(View.VISIBLE); 1173 1174 LogUtil.i( 1175 "VideoCallFragment.updateBlurredImageView", 1176 "took %d millis", 1177 (SystemClock.elapsedRealtime() - startTimeMillis)); 1178 } 1179 1180 private void updateOverlayBackground() { 1181 if (isInGreenScreenMode) { 1182 // We want to darken the preview view to make text and buttons readable. The fullscreen 1183 // background is below the preview view so use the green screen background instead. 1184 animateSetVisibility(greenScreenBackgroundView, View.VISIBLE); 1185 animateSetVisibility(fullscreenBackgroundView, View.GONE); 1186 } else if (!isInFullscreenMode) { 1187 // We want to darken the remote view to make text and buttons readable. The green screen 1188 // background is above the preview view so it would darken the preview too. Use the fullscreen 1189 // background instead. 1190 animateSetVisibility(greenScreenBackgroundView, View.GONE); 1191 animateSetVisibility(fullscreenBackgroundView, View.VISIBLE); 1192 } else { 1193 animateSetVisibility(greenScreenBackgroundView, View.GONE); 1194 animateSetVisibility(fullscreenBackgroundView, View.GONE); 1195 } 1196 } 1197 1198 private void updateMutePreviewOverlayVisibility() { 1199 // Normally the mute overlay shows on the bottom right of the preview bubble. In green screen 1200 // mode the preview is fullscreen so there's no where to anchor it. 1201 mutePreviewOverlay.setVisibility( 1202 muteButton.isChecked() && !isInGreenScreenMode ? View.VISIBLE : View.GONE); 1203 } 1204 1205 private static void animateSetVisibility(final View view, final int visibility) { 1206 if (view.getVisibility() == visibility) { 1207 return; 1208 } 1209 1210 int startAlpha; 1211 int endAlpha; 1212 if (visibility == View.GONE) { 1213 startAlpha = 1; 1214 endAlpha = 0; 1215 } else if (visibility == View.VISIBLE) { 1216 startAlpha = 0; 1217 endAlpha = 1; 1218 } else { 1219 Assert.fail(); 1220 return; 1221 } 1222 1223 view.setAlpha(startAlpha); 1224 view.setVisibility(View.VISIBLE); 1225 view.animate() 1226 .alpha(endAlpha) 1227 .withEndAction( 1228 new Runnable() { 1229 @Override 1230 public void run() { 1231 view.setVisibility(visibility); 1232 } 1233 }) 1234 .start(); 1235 } 1236 1237 private static void blur(Context context, Bitmap image, float blurRadius) { 1238 RenderScript renderScript = RenderScript.create(context); 1239 ScriptIntrinsicBlur blurScript = 1240 ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)); 1241 Allocation allocationIn = Allocation.createFromBitmap(renderScript, image); 1242 Allocation allocationOut = Allocation.createFromBitmap(renderScript, image); 1243 blurScript.setRadius(blurRadius); 1244 blurScript.setInput(allocationIn); 1245 blurScript.forEach(allocationOut); 1246 allocationOut.copyTo(image); 1247 blurScript.destroy(); 1248 allocationIn.destroy(); 1249 allocationOut.destroy(); 1250 } 1251 1252 @Override 1253 public void onSystemUiVisibilityChange(int visibility) { 1254 boolean navBarVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; 1255 videoCallScreenDelegate.onSystemUiVisibilityChange(navBarVisible); 1256 } 1257 1258 private void checkCameraPermission() { 1259 // Checks if user has consent of camera permission and the permission is granted. 1260 // If camera permission is revoked, shows system permission dialog. 1261 // If camera permission is granted but user doesn't have consent of camera permission 1262 // (which means it's first time making video call), shows custom dialog instead. This 1263 // will only be shown to user once. 1264 if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(getContext())) { 1265 videoCallScreenDelegate.onCameraPermissionDialogShown(); 1266 if (!VideoUtils.hasCameraPermission(getContext())) { 1267 requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); 1268 } else { 1269 PermissionsUtil.showCameraPermissionToast(getContext()); 1270 videoCallScreenDelegate.onCameraPermissionGranted(); 1271 } 1272 } 1273 } 1274 } 1275 1276