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