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