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