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