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