• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.OnSystemUiVisibilityChangeListener;
48 import android.view.ViewGroup;
49 import android.view.ViewGroup.MarginLayoutParams;
50 import android.view.ViewOutlineProvider;
51 import android.view.ViewTreeObserver;
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.incallui.audioroute.AudioRouteSelectorDialogFragment;
64 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
65 import com.android.incallui.contactgrid.ContactGridManager;
66 import com.android.incallui.hold.OnHoldFragment;
67 import com.android.incallui.incall.protocol.InCallButtonIds;
68 import com.android.incallui.incall.protocol.InCallButtonIdsExtension;
69 import com.android.incallui.incall.protocol.InCallButtonUi;
70 import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
71 import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
72 import com.android.incallui.incall.protocol.InCallScreen;
73 import com.android.incallui.incall.protocol.InCallScreenDelegate;
74 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
75 import com.android.incallui.incall.protocol.PrimaryCallState;
76 import com.android.incallui.incall.protocol.PrimaryInfo;
77 import com.android.incallui.incall.protocol.SecondaryInfo;
78 import com.android.incallui.video.impl.CameraPermissionDialogFragment.CameraPermissionDialogCallback;
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         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 
newInstance(String callId)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
onCreate(@ullable Bundle savedInstanceState)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
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)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
onCreateView( LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle)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
onViewCreated(View view, @Nullable Bundle bundle)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
onSaveInstanceState(Bundle outState)305   public void onSaveInstanceState(Bundle outState) {
306     super.onSaveInstanceState(outState);
307     inCallButtonUiDelegate.onSaveInstanceState(outState);
308   }
309 
310   @Override
onDestroyView()311   public void onDestroyView() {
312     super.onDestroyView();
313     LogUtil.i("VideoCallFragment.onDestroyView", null);
314     inCallButtonUiDelegate.onInCallButtonUiUnready();
315     inCallScreenDelegate.onInCallScreenUnready();
316   }
317 
318   @Override
onAttach(Context context)319   public void onAttach(Context context) {
320     super.onAttach(context);
321     if (savedSecondaryInfo != null) {
322       setSecondary(savedSecondaryInfo);
323     }
324   }
325 
326   @Override
onStart()327   public void onStart() {
328     super.onStart();
329     LogUtil.i("VideoCallFragment.onStart", null);
330     onVideoScreenStart();
331   }
332 
333   @Override
onVideoScreenStart()334   public void onVideoScreenStart() {
335     inCallButtonUiDelegate.refreshMuteState();
336     videoCallScreenDelegate.onVideoCallScreenUiReady();
337     getView().postDelayed(cameraPermissionDialogRunnable, CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS);
338   }
339 
340   @Override
onResume()341   public void onResume() {
342     super.onResume();
343     LogUtil.i("VideoCallFragment.onResume", null);
344     inCallScreenDelegate.onInCallScreenResumed();
345   }
346 
347   @Override
onPause()348   public void onPause() {
349     super.onPause();
350     LogUtil.i("VideoCallFragment.onPause", null);
351     inCallScreenDelegate.onInCallScreenPaused();
352   }
353 
354   @Override
onStop()355   public void onStop() {
356     super.onStop();
357     LogUtil.i("VideoCallFragment.onStop", null);
358     onVideoScreenStop();
359   }
360 
361   @Override
onVideoScreenStop()362   public void onVideoScreenStop() {
363     getView().removeCallbacks(cameraPermissionDialogRunnable);
364     videoCallScreenDelegate.onVideoCallScreenUiUnready();
365   }
366 
exitFullscreenMode()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 
showSystemUI()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 */
hideSystemUI()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 
getControlsOffsetEndHidden(View controls)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 
getSwitchOnHoldOffsetEndHidden(View swapCallButton)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 
getContactGridOffsetEndHidden(View view)488   private Point getContactGridOffsetEndHidden(View view) {
489     return new Point(0, getOffsetTop(view));
490   }
491 
getEndCallOffsetEndHidden(View endCallButton)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 
getPreviewOffsetStartShown()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 
getAllPreviewRelatedViews()516   private View[] getAllPreviewRelatedViews() {
517     return new View[] {
518       previewTextureView, previewOffOverlay, previewOffBlurredImageView, mutePreviewOverlay,
519     };
520   }
521 
getOffsetTop(View view)522   private int getOffsetTop(View view) {
523     return -(view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).topMargin);
524   }
525 
getOffsetBottom(View view)526   private int getOffsetBottom(View view) {
527     return view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).bottomMargin;
528   }
529 
getOffsetStart(View view)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 
getOffsetEnd(View view)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 
enterFullscreenMode()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
onClick(View v)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
onCheckedChanged(CheckableImageButton button, boolean isChecked)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
showVideoViews( boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld)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
onLocalVideoDimensionsChanged()675   public void onLocalVideoDimensionsChanged() {
676     LogUtil.i("VideoCallFragment.onLocalVideoDimensionsChanged", null);
677     updatePreviewVideoScaling();
678   }
679 
680   @Override
onLocalVideoOrientationChanged()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
onRemoteVideoDimensionsChanged()688   public void onRemoteVideoDimensionsChanged() {
689     LogUtil.i("VideoCallFragment.onRemoteVideoDimensionsChanged", null);
690     updateRemoteVideoScaling();
691   }
692 
693   @Override
updateFullscreenAndGreenScreenMode( boolean shouldShowFullscreen, boolean shouldShowGreenScreen)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
getVideoCallScreenFragment()746   public Fragment getVideoCallScreenFragment() {
747     return this;
748   }
749 
750   @Override
751   @NonNull
getCallId()752   public String getCallId() {
753     return Assert.isNotNull(getArguments().getString(ARG_CALL_ID));
754   }
755 
756   @Override
showButton(@nCallButtonIds int buttonId, boolean show)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
enableButton(@nCallButtonIds int buttonId, boolean enable)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
setEnabled(boolean enabled)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
setHold(boolean value)804   public void setHold(boolean value) {
805     LogUtil.i("VideoCallFragment.setHold", "value: " + value);
806   }
807 
808   @Override
setCameraSwitched(boolean isBackFacingCamera)809   public void setCameraSwitched(boolean isBackFacingCamera) {
810     LogUtil.i("VideoCallFragment.setCameraSwitched", "isBackFacingCamera: " + isBackFacingCamera);
811   }
812 
813   @Override
setVideoPaused(boolean isPaused)814   public void setVideoPaused(boolean isPaused) {
815     LogUtil.i("VideoCallFragment.setVideoPaused", "isPaused: " + isPaused);
816     cameraOffButton.setChecked(isPaused);
817   }
818 
819   @Override
setAudioState(CallAudioState audioState)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
updateButtonStates()828   public void updateButtonStates() {
829     LogUtil.i("VideoCallFragment.updateButtonState", null);
830     speakerButtonController.updateButtonState();
831     switchOnHoldCallController.updateButtonState();
832   }
833 
834   @Override
updateInCallButtonUiColors()835   public void updateInCallButtonUiColors() {}
836 
837   @Override
getInCallButtonUiFragment()838   public Fragment getInCallButtonUiFragment() {
839     return this;
840   }
841 
842   @Override
showAudioRouteSelector()843   public void showAudioRouteSelector() {
844     LogUtil.i("VideoCallFragment.showAudioRouteSelector", null);
845     AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
846         .show(getChildFragmentManager(), null);
847   }
848 
849   @Override
onAudioRouteSelected(int audioRoute)850   public void onAudioRouteSelected(int audioRoute) {
851     LogUtil.i("VideoCallFragment.onAudioRouteSelected", "audioRoute: " + audioRoute);
852     inCallButtonUiDelegate.setAudioRoute(audioRoute);
853   }
854 
855   @Override
setPrimary(@onNull PrimaryInfo primaryInfo)856   public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
857     LogUtil.i("VideoCallFragment.setPrimary", primaryInfo.toString());
858     contactGridManager.setPrimary(primaryInfo);
859   }
860 
861   @Override
setSecondary(@onNull SecondaryInfo secondaryInfo)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
setCallState(@onNull PrimaryCallState primaryCallState)887   public void setCallState(@NonNull PrimaryCallState primaryCallState) {
888     LogUtil.i("VideoCallFragment.setCallState", primaryCallState.toString());
889     contactGridManager.setCallState(primaryCallState);
890   }
891 
892   @Override
setEndCallButtonEnabled(boolean enabled, boolean animate)893   public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
894     LogUtil.i("VideoCallFragment.setEndCallButtonEnabled", "enabled: " + enabled);
895   }
896 
897   @Override
showManageConferenceCallButton(boolean visible)898   public void showManageConferenceCallButton(boolean visible) {
899     LogUtil.i("VideoCallFragment.showManageConferenceCallButton", "visible: " + visible);
900   }
901 
902   @Override
isManageConferenceVisible()903   public boolean isManageConferenceVisible() {
904     LogUtil.i("VideoCallFragment.isManageConferenceVisible", null);
905     return false;
906   }
907 
908   @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)909   public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
910     contactGridManager.dispatchPopulateAccessibilityEvent(event);
911   }
912 
913   @Override
showNoteSentToast()914   public void showNoteSentToast() {
915     LogUtil.i("VideoCallFragment.showNoteSentToast", null);
916   }
917 
918   @Override
updateInCallScreenColors()919   public void updateInCallScreenColors() {
920     LogUtil.i("VideoCallFragment.updateColors", null);
921   }
922 
923   @Override
onInCallScreenDialpadVisibilityChange(boolean isShowing)924   public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {
925     LogUtil.i("VideoCallFragment.onInCallScreenDialpadVisibilityChange", null);
926   }
927 
928   @Override
getAnswerAndDialpadContainerResourceId()929   public int getAnswerAndDialpadContainerResourceId() {
930     return 0;
931   }
932 
933   @Override
getInCallScreenFragment()934   public Fragment getInCallScreenFragment() {
935     return this;
936   }
937 
938   @Override
isShowingLocationUi()939   public boolean isShowingLocationUi() {
940     return false;
941   }
942 
943   @Override
showLocationUi(Fragment locationUi)944   public void showLocationUi(Fragment locationUi) {
945     LogUtil.e("VideoCallFragment.showLocationUi", "Emergency video calling not supported");
946     // Do nothing
947   }
948 
updatePreviewVideoScaling()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 
updateRemoteVideoScaling()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 
isLandscape()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 
enterGreenScreenMode()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 
exitGreenScreenMode()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 
updateVideoOffViews()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 
updateBlurredImageView( TextureView textureView, ImageView blurredImageView, boolean isVideoEnabled, float blurRadius, float scaleFactor)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 
updateOverlayBackground()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 
updateMutePreviewOverlayVisibility()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 
animateSetVisibility(final View view, final int visibility)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 
blur(Context context, Bitmap image, float blurRadius)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
onSystemUiVisibilityChange(int visibility)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
onCameraPermissionGranted()1232   public void onCameraPermissionGranted() {
1233     videoCallScreenDelegate.onCameraPermissionGranted();
1234   }
1235 
checkCameraPermission()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