summaryrefslogtreecommitdiff
path: root/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
diff options
context:
space:
mode:
authorEric Erfanian <erfanian@google.com>2017-03-24 09:31:16 -0700
committerEric Erfanian <erfanian@google.com>2017-03-27 08:56:19 -0700
commit9050823ccf6f512e06ad65c8a741cb17cbc4a833 (patch)
treee454c9e0fa20b8384fa87d4e6309bb8b9e3aa2a4 /java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
parentdd9d50805f1b49dff1fe3787740393d726124cdb (diff)
Update AOSP Dialer source from internal google3 repository at
cl/151128062 Test: make, treehugger This CL updates the AOSP Dialer source with all the changes that have gone into the private google3 repository. This includes all the changes from cl/150756069 (3/21/2017) to cl/151128062 (3/24/2017). Notable this release: - Explicitly enumerate host and target dependencies. - Update proguard flag references. This goal of these drops is to keep the AOSP source in sync with the internal google3 repository. Currently these sync are done by hand with very minor modifications to the internal source code. See the Android.mk file for list of modifications. Our current goal is to do frequent drops (daily if possible) and eventually switched to an automated process. Bug: 33210202 36511925 Addresses 33210202 - Proguard support 36511925 - Compiler warnings when building against platform sdk Change-Id: I448ec3b3f2358886859cf7a4ef76a8fcef3244ae
Diffstat (limited to 'java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java')
-rw-r--r--java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java1081
1 files changed, 1081 insertions, 0 deletions
diff --git a/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java b/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
new file mode 100644
index 000000000..c969fb05a
--- /dev/null
+++ b/java/com/android/incallui/video/impl/SurfaceViewVideoCallFragment.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui.video.impl;
+
+import android.Manifest.permission;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.drawable.Animatable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.animation.FastOutLinearInInterpolator;
+import android.support.v4.view.animation.LinearOutSlowInInterpolator;
+import android.telecom.CallAudioState;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnSystemUiVisibilityChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.FragmentUtils;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.ActivityCompat;
+import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment;
+import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
+import com.android.incallui.contactgrid.ContactGridManager;
+import com.android.incallui.hold.OnHoldFragment;
+import com.android.incallui.incall.protocol.InCallButtonIds;
+import com.android.incallui.incall.protocol.InCallButtonIdsExtension;
+import com.android.incallui.incall.protocol.InCallButtonUi;
+import com.android.incallui.incall.protocol.InCallButtonUiDelegate;
+import com.android.incallui.incall.protocol.InCallButtonUiDelegateFactory;
+import com.android.incallui.incall.protocol.InCallScreen;
+import com.android.incallui.incall.protocol.InCallScreenDelegate;
+import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
+import com.android.incallui.incall.protocol.PrimaryCallState;
+import com.android.incallui.incall.protocol.PrimaryInfo;
+import com.android.incallui.incall.protocol.SecondaryInfo;
+import com.android.incallui.video.impl.CheckableImageButton.OnCheckedChangeListener;
+import com.android.incallui.video.protocol.VideoCallScreen;
+import com.android.incallui.video.protocol.VideoCallScreenDelegate;
+import com.android.incallui.video.protocol.VideoCallScreenDelegateFactory;
+import com.android.incallui.videotech.utils.VideoUtils;
+
+/**
+ * Contains UI elements for a video call.
+ *
+ * <p>This version is used by RCS Video Share since Dreamchip requires a SurfaceView instead of the
+ * TextureView, which is present in {@link VideoCallFragment} and used by IMS.
+ */
+public class SurfaceViewVideoCallFragment extends Fragment
+ implements InCallScreen,
+ InCallButtonUi,
+ VideoCallScreen,
+ OnClickListener,
+ OnCheckedChangeListener,
+ AudioRouteSelectorPresenter,
+ OnSystemUiVisibilityChangeListener {
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String ARG_CALL_ID = "call_id";
+
+ private static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
+ private static final String CAMERA_PERMISSION_DIALOG_FRAMENT_TAG =
+ "CameraPermissionDialogFragment";
+ private static final long CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS = 2000L;
+ private static final long VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS = 2000L;
+
+ private InCallScreenDelegate inCallScreenDelegate;
+ private VideoCallScreenDelegate videoCallScreenDelegate;
+ private InCallButtonUiDelegate inCallButtonUiDelegate;
+ private View endCallButton;
+ private CheckableImageButton speakerButton;
+ private SpeakerButtonController speakerButtonController;
+ private CheckableImageButton muteButton;
+ private CheckableImageButton cameraOffButton;
+ private ImageButton swapCameraButton;
+ private View switchOnHoldButton;
+ private View onHoldContainer;
+ private SwitchOnHoldCallController switchOnHoldCallController;
+ private TextView remoteVideoOff;
+ private View mutePreviewOverlay;
+ private View previewOffOverlay;
+ private ImageView previewOffBlurredImageView;
+ private View controls;
+ private View controlsContainer;
+ private SurfaceView previewSurfaceView;
+ private SurfaceView remoteSurfaceView;
+ private View greenScreenBackgroundView;
+ private View fullscreenBackgroundView;
+ private boolean shouldShowRemote;
+ private boolean shouldShowPreview;
+ private boolean isInFullscreenMode;
+ private boolean isInGreenScreenMode;
+ private boolean hasInitializedScreenModes;
+ private boolean isRemotelyHeld;
+ private ContactGridManager contactGridManager;
+ private SecondaryInfo savedSecondaryInfo;
+ private final Runnable cameraPermissionDialogRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ if (videoCallScreenDelegate.shouldShowCameraPermissionDialog()) {
+ LogUtil.i("VideoCallFragment.cameraPermissionDialogRunnable", "showing dialog");
+ checkCameraPermission();
+ }
+ }
+ };
+
+ public static SurfaceViewVideoCallFragment newInstance(String callId) {
+ Bundle bundle = new Bundle();
+ bundle.putString(ARG_CALL_ID, Assert.isNotNull(callId));
+
+ SurfaceViewVideoCallFragment instance = new SurfaceViewVideoCallFragment();
+ instance.setArguments(bundle);
+ return instance;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LogUtil.i("VideoCallFragment.onCreate", null);
+
+ inCallButtonUiDelegate =
+ FragmentUtils.getParent(this, InCallButtonUiDelegateFactory.class)
+ .newInCallButtonUiDelegate();
+ if (savedInstanceState != null) {
+ inCallButtonUiDelegate.onRestoreInstanceState(savedInstanceState);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission granted.");
+ videoCallScreenDelegate.onCameraPermissionGranted();
+ } else {
+ LogUtil.i("VideoCallFragment.onRequestPermissionsResult", "Camera permission denied.");
+ }
+ }
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(
+ LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
+ LogUtil.i("VideoCallFragment.onCreateView", null);
+
+ View view =
+ layoutInflater.inflate(
+ isLandscape()
+ ? R.layout.frag_videocall_land_surfaceview
+ : R.layout.frag_videocall_surfaceview,
+ viewGroup,
+ false);
+ contactGridManager =
+ new ContactGridManager(view, null /* no avatar */, 0, false /* showAnonymousAvatar */);
+
+ controls = view.findViewById(R.id.videocall_video_controls);
+ controls.setVisibility(
+ ActivityCompat.isInMultiWindowMode(getActivity()) ? View.GONE : View.VISIBLE);
+ controlsContainer = view.findViewById(R.id.videocall_video_controls_container);
+ speakerButton = (CheckableImageButton) view.findViewById(R.id.videocall_speaker_button);
+ muteButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_button);
+ muteButton.setOnCheckedChangeListener(this);
+ mutePreviewOverlay = view.findViewById(R.id.videocall_video_preview_mute_overlay);
+ cameraOffButton = (CheckableImageButton) view.findViewById(R.id.videocall_mute_video);
+ cameraOffButton.setOnCheckedChangeListener(this);
+ previewOffOverlay = view.findViewById(R.id.videocall_video_preview_off_overlay);
+ previewOffBlurredImageView =
+ (ImageView) view.findViewById(R.id.videocall_preview_off_blurred_image_view);
+ swapCameraButton = (ImageButton) view.findViewById(R.id.videocall_switch_video);
+ swapCameraButton.setOnClickListener(this);
+ view.findViewById(R.id.videocall_switch_controls)
+ .setVisibility(
+ ActivityCompat.isInMultiWindowMode(getActivity()) ? View.GONE : View.VISIBLE);
+ switchOnHoldButton = view.findViewById(R.id.videocall_switch_on_hold);
+ onHoldContainer = view.findViewById(R.id.videocall_on_hold_banner);
+ remoteVideoOff = (TextView) view.findViewById(R.id.videocall_remote_video_off);
+ remoteVideoOff.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ endCallButton = view.findViewById(R.id.videocall_end_call);
+ endCallButton.setOnClickListener(this);
+ previewSurfaceView = (SurfaceView) view.findViewById(R.id.videocall_video_preview);
+ previewSurfaceView.setClipToOutline(true);
+ previewOffOverlay.setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ checkCameraPermission();
+ }
+ });
+ remoteSurfaceView = (SurfaceView) view.findViewById(R.id.videocall_video_remote);
+ greenScreenBackgroundView = view.findViewById(R.id.videocall_green_screen_background);
+ fullscreenBackgroundView = view.findViewById(R.id.videocall_fullscreen_background);
+
+ // We need the texture view size to be able to scale the remote video. At this point the view
+ // layout won't be complete so add a layout listener.
+ ViewTreeObserver observer = remoteSurfaceView.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ LogUtil.i("VideoCallFragment.onGlobalLayout", null);
+ updateVideoOffViews();
+ // Remove the listener so we don't continually re-layout.
+ ViewTreeObserver observer = remoteSurfaceView.getViewTreeObserver();
+ if (observer.isAlive()) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle bundle) {
+ super.onViewCreated(view, bundle);
+ LogUtil.i("VideoCallFragment.onViewCreated", null);
+
+ inCallScreenDelegate =
+ FragmentUtils.getParentUnsafe(this, InCallScreenDelegateFactory.class)
+ .newInCallScreenDelegate();
+ videoCallScreenDelegate =
+ FragmentUtils.getParentUnsafe(this, VideoCallScreenDelegateFactory.class)
+ .newVideoCallScreenDelegate(this);
+
+ speakerButtonController =
+ new SpeakerButtonController(speakerButton, inCallButtonUiDelegate, videoCallScreenDelegate);
+ switchOnHoldCallController =
+ new SwitchOnHoldCallController(
+ switchOnHoldButton, onHoldContainer, inCallScreenDelegate, videoCallScreenDelegate);
+
+ videoCallScreenDelegate.initVideoCallScreenDelegate(getContext(), this);
+
+ inCallScreenDelegate.onInCallScreenDelegateInit(this);
+ inCallScreenDelegate.onInCallScreenReady();
+ inCallButtonUiDelegate.onInCallButtonUiReady(this);
+
+ view.setOnSystemUiVisibilityChangeListener(this);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ inCallButtonUiDelegate.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ LogUtil.i("VideoCallFragment.onDestroyView", null);
+ inCallButtonUiDelegate.onInCallButtonUiUnready();
+ inCallScreenDelegate.onInCallScreenUnready();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (savedSecondaryInfo != null) {
+ setSecondary(savedSecondaryInfo);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ LogUtil.i("VideoCallFragment.onStart", null);
+ onVideoScreenStart();
+ }
+
+ @Override
+ public void onVideoScreenStart() {
+ inCallButtonUiDelegate.refreshMuteState();
+ videoCallScreenDelegate.onVideoCallScreenUiReady();
+ getView().postDelayed(cameraPermissionDialogRunnable, CAMERA_PERMISSION_DIALOG_DELAY_IN_MILLIS);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ LogUtil.i("VideoCallFragment.onResume", null);
+ inCallScreenDelegate.onInCallScreenResumed();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ LogUtil.i("VideoCallFragment.onPause", null);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ LogUtil.i("VideoCallFragment.onStop", null);
+ onVideoScreenStop();
+ }
+
+ @Override
+ public void onVideoScreenStop() {
+ getView().removeCallbacks(cameraPermissionDialogRunnable);
+ videoCallScreenDelegate.onVideoCallScreenUiUnready();
+ }
+
+ private void exitFullscreenMode() {
+ LogUtil.i("VideoCallFragment.exitFullscreenMode", null);
+
+ if (!getView().isAttachedToWindow()) {
+ LogUtil.i("VideoCallFragment.exitFullscreenMode", "not attached");
+ return;
+ }
+
+ showSystemUI();
+
+ LinearOutSlowInInterpolator linearOutSlowInInterpolator = new LinearOutSlowInInterpolator();
+
+ // Animate the controls to the shown state.
+ controls
+ .animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(linearOutSlowInInterpolator)
+ .alpha(1)
+ .start();
+
+ // Animate onHold to the shown state.
+ switchOnHoldButton
+ .animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(linearOutSlowInInterpolator)
+ .alpha(1)
+ .withStartAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ switchOnHoldCallController.setOnScreen();
+ }
+ });
+
+ View contactGridView = contactGridManager.getContainerView();
+ // Animate contact grid to the shown state.
+ contactGridView
+ .animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(linearOutSlowInInterpolator)
+ .alpha(1)
+ .withStartAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ contactGridManager.show();
+ }
+ });
+
+ endCallButton
+ .animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(linearOutSlowInInterpolator)
+ .alpha(1)
+ .withStartAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ endCallButton.setVisibility(View.VISIBLE);
+ }
+ })
+ .start();
+
+ // Animate all the preview controls up to make room for the navigation bar.
+ // In green screen mode we don't need this because the preview takes up the whole screen and has
+ // a fixed position.
+ if (!isInGreenScreenMode) {
+ Point previewOffsetStartShown = getPreviewOffsetStartShown();
+ for (View view : getAllPreviewRelatedViews()) {
+ // Animate up with the preview offset above the navigation bar.
+ view.animate()
+ .translationX(previewOffsetStartShown.x)
+ .translationY(previewOffsetStartShown.y)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .start();
+ }
+ }
+
+ updateOverlayBackground();
+ }
+
+ private void showSystemUI() {
+ View view = getView();
+ if (view != null) {
+ // Code is more expressive with all flags present, even though some may be combined
+ //noinspection PointlessBitwiseExpression
+ view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+ }
+
+ /** Set view flags to hide the system UI. System UI will return on any touch event */
+ private void hideSystemUI() {
+ View view = getView();
+ if (view != null) {
+ view.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+ }
+
+ private Point getControlsOffsetEndHidden(View controls) {
+ if (isLandscape()) {
+ return new Point(0, getOffsetBottom(controls));
+ } else {
+ return new Point(getOffsetStart(controls), 0);
+ }
+ }
+
+ private Point getSwitchOnHoldOffsetEndHidden(View swapCallButton) {
+ if (isLandscape()) {
+ return new Point(0, getOffsetTop(swapCallButton));
+ } else {
+ return new Point(getOffsetEnd(swapCallButton), 0);
+ }
+ }
+
+ private Point getContactGridOffsetEndHidden(View view) {
+ return new Point(0, getOffsetTop(view));
+ }
+
+ private Point getEndCallOffsetEndHidden(View endCallButton) {
+ if (isLandscape()) {
+ return new Point(getOffsetEnd(endCallButton), 0);
+ } else {
+ return new Point(0, ((MarginLayoutParams) endCallButton.getLayoutParams()).bottomMargin);
+ }
+ }
+
+ private Point getPreviewOffsetStartShown() {
+ // No insets in multiwindow mode, and rootWindowInsets will get the display's insets.
+ if (ActivityCompat.isInMultiWindowMode(getActivity())) {
+ return new Point();
+ }
+ if (isLandscape()) {
+ int stableInsetEnd =
+ getView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? getView().getRootWindowInsets().getStableInsetLeft()
+ : -getView().getRootWindowInsets().getStableInsetRight();
+ return new Point(stableInsetEnd, 0);
+ } else {
+ return new Point(0, -getView().getRootWindowInsets().getStableInsetBottom());
+ }
+ }
+
+ private View[] getAllPreviewRelatedViews() {
+ return new View[] {
+ previewSurfaceView, previewOffOverlay, previewOffBlurredImageView, mutePreviewOverlay,
+ };
+ }
+
+ private int getOffsetTop(View view) {
+ return -(view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).topMargin);
+ }
+
+ private int getOffsetBottom(View view) {
+ return view.getHeight() + ((MarginLayoutParams) view.getLayoutParams()).bottomMargin;
+ }
+
+ private int getOffsetStart(View view) {
+ int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginStart();
+ if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ offset = -offset;
+ }
+ return -offset;
+ }
+
+ private int getOffsetEnd(View view) {
+ int offset = view.getWidth() + ((MarginLayoutParams) view.getLayoutParams()).getMarginEnd();
+ if (view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ offset = -offset;
+ }
+ return offset;
+ }
+
+ private void enterFullscreenMode() {
+ LogUtil.i("VideoCallFragment.enterFullscreenMode", null);
+
+ hideSystemUI();
+
+ Interpolator fastOutLinearInInterpolator = new FastOutLinearInInterpolator();
+
+ // Animate controls to the hidden state.
+ Point offset = getControlsOffsetEndHidden(controls);
+ controls
+ .animate()
+ .translationX(offset.x)
+ .translationY(offset.y)
+ .setInterpolator(fastOutLinearInInterpolator)
+ .alpha(0)
+ .start();
+
+ // Animate onHold to the hidden state.
+ offset = getSwitchOnHoldOffsetEndHidden(switchOnHoldButton);
+ switchOnHoldButton
+ .animate()
+ .translationX(offset.x)
+ .translationY(offset.y)
+ .setInterpolator(fastOutLinearInInterpolator)
+ .alpha(0);
+
+ View contactGridView = contactGridManager.getContainerView();
+ // Animate contact grid to the hidden state.
+ offset = getContactGridOffsetEndHidden(contactGridView);
+ contactGridView
+ .animate()
+ .translationX(offset.x)
+ .translationY(offset.y)
+ .setInterpolator(fastOutLinearInInterpolator)
+ .alpha(0);
+
+ offset = getEndCallOffsetEndHidden(endCallButton);
+ // Use a fast out interpolator to quickly fade out the button. This is important because the
+ // button can't draw under the navigation bar which means that it'll look weird if it just
+ // abruptly disappears when it reaches the edge of the naivgation bar.
+ endCallButton
+ .animate()
+ .translationX(offset.x)
+ .translationY(offset.y)
+ .setInterpolator(fastOutLinearInInterpolator)
+ .alpha(0)
+ .withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ endCallButton.setVisibility(View.INVISIBLE);
+ }
+ })
+ .setInterpolator(new FastOutLinearInInterpolator())
+ .start();
+
+ // Animate all the preview controls down now that the navigation bar is hidden.
+ // In green screen mode we don't need this because the preview takes up the whole screen and has
+ // a fixed position.
+ if (!isInGreenScreenMode) {
+ for (View view : getAllPreviewRelatedViews()) {
+ // Animate down with the navigation bar hidden.
+ view.animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(new AccelerateDecelerateInterpolator())
+ .start();
+ }
+ }
+ updateOverlayBackground();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == endCallButton) {
+ LogUtil.i("VideoCallFragment.onClick", "end call button clicked");
+ inCallButtonUiDelegate.onEndCallClicked();
+ videoCallScreenDelegate.resetAutoFullscreenTimer();
+ } else if (v == swapCameraButton) {
+ if (swapCameraButton.getDrawable() instanceof Animatable) {
+ ((Animatable) swapCameraButton.getDrawable()).start();
+ }
+ inCallButtonUiDelegate.toggleCameraClicked();
+ videoCallScreenDelegate.resetAutoFullscreenTimer();
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CheckableImageButton button, boolean isChecked) {
+ if (button == cameraOffButton) {
+ if (!isChecked && !VideoUtils.hasCameraPermissionAndAllowedByUser(getContext())) {
+ LogUtil.i("VideoCallFragment.onCheckedChanged", "show camera permission dialog");
+ checkCameraPermission();
+ } else {
+ inCallButtonUiDelegate.pauseVideoClicked(isChecked);
+ videoCallScreenDelegate.resetAutoFullscreenTimer();
+ }
+ } else if (button == muteButton) {
+ inCallButtonUiDelegate.muteClicked(isChecked, true /* clickedByUser */);
+ videoCallScreenDelegate.resetAutoFullscreenTimer();
+ }
+ }
+
+ @Override
+ public void showVideoViews(
+ boolean shouldShowPreview, boolean shouldShowRemote, boolean isRemotelyHeld) {
+ LogUtil.i(
+ "VideoCallFragment.showVideoViews",
+ "showPreview: %b, shouldShowRemote: %b",
+ shouldShowPreview,
+ shouldShowRemote);
+ this.shouldShowPreview = shouldShowPreview;
+ this.shouldShowRemote = shouldShowRemote;
+ this.isRemotelyHeld = isRemotelyHeld;
+
+ videoCallScreenDelegate.setSurfaceViews(previewSurfaceView, remoteSurfaceView);
+ updateVideoOffViews();
+ }
+
+ /**
+ * This method scales the video feed inside the texture view, it doesn't change the texture view's
+ * size. In the old UI we would change the view size to match the aspect ratio of the video. In
+ * the new UI the view is always square (with the circular clip) so we have to do additional work
+ * to make sure the non-square video doesn't look squished.
+ */
+ @Override
+ public void onLocalVideoDimensionsChanged() {
+ LogUtil.i("VideoCallFragment.onLocalVideoDimensionsChanged", null);
+ }
+
+ @Override
+ public void onLocalVideoOrientationChanged() {
+ LogUtil.i("VideoCallFragment.onLocalVideoOrientationChanged", null);
+ }
+
+ /** Called when the remote video's dimensions change. */
+ @Override
+ public void onRemoteVideoDimensionsChanged() {
+ LogUtil.i("VideoCallFragment.onRemoteVideoDimensionsChanged", null);
+ }
+
+ @Override
+ public void updateFullscreenAndGreenScreenMode(
+ boolean shouldShowFullscreen, boolean shouldShowGreenScreen) {
+ LogUtil.i(
+ "VideoCallFragment.updateFullscreenAndGreenScreenMode",
+ "shouldShowFullscreen: %b, shouldShowGreenScreen: %b",
+ shouldShowFullscreen,
+ shouldShowGreenScreen);
+
+ if (getActivity() == null) {
+ LogUtil.i("VideoCallFragment.updateFullscreenAndGreenScreenMode", "not attached to activity");
+ return;
+ }
+
+ // Check if anything is actually going to change. The first time this function is called we
+ // force a change by checking the hasInitializedScreenModes flag. We also force both fullscreen
+ // and green screen modes to update even if only one has changed. That's because they both
+ // depend on each other.
+ if (hasInitializedScreenModes
+ && shouldShowGreenScreen == isInGreenScreenMode
+ && shouldShowFullscreen == isInFullscreenMode) {
+ LogUtil.i(
+ "VideoCallFragment.updateFullscreenAndGreenScreenMode", "no change to screen modes");
+ return;
+ }
+ hasInitializedScreenModes = true;
+ isInGreenScreenMode = shouldShowGreenScreen;
+ isInFullscreenMode = shouldShowFullscreen;
+
+ if (getView().isAttachedToWindow() && !ActivityCompat.isInMultiWindowMode(getActivity())) {
+ controlsContainer.onApplyWindowInsets(getView().getRootWindowInsets());
+ }
+ if (shouldShowGreenScreen) {
+ enterGreenScreenMode();
+ } else {
+ exitGreenScreenMode();
+ }
+ if (shouldShowFullscreen) {
+ enterFullscreenMode();
+ } else {
+ exitFullscreenMode();
+ }
+ updateVideoOffViews();
+
+ OnHoldFragment onHoldFragment =
+ ((OnHoldFragment)
+ getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner));
+ if (onHoldFragment != null) {
+ onHoldFragment.setPadTopInset(!isInFullscreenMode);
+ }
+ }
+
+ @Override
+ public Fragment getVideoCallScreenFragment() {
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getCallId() {
+ return Assert.isNotNull(getArguments().getString(ARG_CALL_ID));
+ }
+
+ @Override
+ public void showButton(@InCallButtonIds int buttonId, boolean show) {
+ LogUtil.v(
+ "VideoCallFragment.showButton",
+ "buttonId: %s, show: %b",
+ InCallButtonIdsExtension.toString(buttonId),
+ show);
+ if (buttonId == InCallButtonIds.BUTTON_AUDIO) {
+ speakerButtonController.setEnabled(show);
+ } else if (buttonId == InCallButtonIds.BUTTON_MUTE) {
+ muteButton.setEnabled(show);
+ } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) {
+ cameraOffButton.setEnabled(show);
+ } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) {
+ switchOnHoldCallController.setVisible(show);
+ } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_CAMERA) {
+ swapCameraButton.setEnabled(show);
+ }
+ }
+
+ @Override
+ public void enableButton(@InCallButtonIds int buttonId, boolean enable) {
+ LogUtil.v(
+ "VideoCallFragment.setEnabled",
+ "buttonId: %s, enable: %b",
+ InCallButtonIdsExtension.toString(buttonId),
+ enable);
+ if (buttonId == InCallButtonIds.BUTTON_AUDIO) {
+ speakerButtonController.setEnabled(enable);
+ } else if (buttonId == InCallButtonIds.BUTTON_MUTE) {
+ muteButton.setEnabled(enable);
+ } else if (buttonId == InCallButtonIds.BUTTON_PAUSE_VIDEO) {
+ cameraOffButton.setEnabled(enable);
+ } else if (buttonId == InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY) {
+ switchOnHoldCallController.setEnabled(enable);
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ LogUtil.v("VideoCallFragment.setEnabled", "enabled: " + enabled);
+ speakerButtonController.setEnabled(enabled);
+ muteButton.setEnabled(enabled);
+ cameraOffButton.setEnabled(enabled);
+ switchOnHoldCallController.setEnabled(enabled);
+ }
+
+ @Override
+ public void setHold(boolean value) {
+ LogUtil.i("VideoCallFragment.setHold", "value: " + value);
+ }
+
+ @Override
+ public void setCameraSwitched(boolean isBackFacingCamera) {
+ LogUtil.i("VideoCallFragment.setCameraSwitched", "isBackFacingCamera: " + isBackFacingCamera);
+ }
+
+ @Override
+ public void setVideoPaused(boolean isPaused) {
+ LogUtil.i("VideoCallFragment.setVideoPaused", "isPaused: " + isPaused);
+ cameraOffButton.setChecked(isPaused);
+ }
+
+ @Override
+ public void setAudioState(CallAudioState audioState) {
+ LogUtil.i("VideoCallFragment.setAudioState", "audioState: " + audioState);
+ speakerButtonController.setAudioState(audioState);
+ muteButton.setChecked(audioState.isMuted());
+ updateMutePreviewOverlayVisibility();
+ }
+
+ @Override
+ public void updateButtonStates() {
+ LogUtil.i("VideoCallFragment.updateButtonState", null);
+ speakerButtonController.updateButtonState();
+ switchOnHoldCallController.updateButtonState();
+ }
+
+ @Override
+ public void updateInCallButtonUiColors() {}
+
+ @Override
+ public Fragment getInCallButtonUiFragment() {
+ return this;
+ }
+
+ @Override
+ public void showAudioRouteSelector() {
+ LogUtil.i("VideoCallFragment.showAudioRouteSelector", null);
+ AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
+ .show(getChildFragmentManager(), null);
+ }
+
+ @Override
+ public void onAudioRouteSelected(int audioRoute) {
+ LogUtil.i("VideoCallFragment.onAudioRouteSelected", "audioRoute: " + audioRoute);
+ inCallButtonUiDelegate.setAudioRoute(audioRoute);
+ }
+
+ @Override
+ public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
+ LogUtil.i("VideoCallFragment.setPrimary", primaryInfo.toString());
+ contactGridManager.setPrimary(primaryInfo);
+ }
+
+ @Override
+ public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {
+ LogUtil.i("VideoCallFragment.setSecondary", secondaryInfo.toString());
+ if (!isAdded()) {
+ savedSecondaryInfo = secondaryInfo;
+ return;
+ }
+ savedSecondaryInfo = null;
+ switchOnHoldCallController.setSecondaryInfo(secondaryInfo);
+ updateButtonStates();
+ FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+ Fragment oldBanner = getChildFragmentManager().findFragmentById(R.id.videocall_on_hold_banner);
+ if (secondaryInfo.shouldShow) {
+ OnHoldFragment onHoldFragment = OnHoldFragment.newInstance(secondaryInfo);
+ onHoldFragment.setPadTopInset(!isInFullscreenMode);
+ transaction.replace(R.id.videocall_on_hold_banner, onHoldFragment);
+ } else {
+ if (oldBanner != null) {
+ transaction.remove(oldBanner);
+ }
+ }
+ transaction.setCustomAnimations(R.anim.abc_slide_in_top, R.anim.abc_slide_out_top);
+ transaction.commitAllowingStateLoss();
+ }
+
+ @Override
+ public void setCallState(@NonNull PrimaryCallState primaryCallState) {
+ LogUtil.i("VideoCallFragment.setCallState", primaryCallState.toString());
+ contactGridManager.setCallState(primaryCallState);
+ }
+
+ @Override
+ public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
+ LogUtil.i("VideoCallFragment.setEndCallButtonEnabled", "enabled: " + enabled);
+ }
+
+ @Override
+ public void showManageConferenceCallButton(boolean visible) {
+ LogUtil.i("VideoCallFragment.showManageConferenceCallButton", "visible: " + visible);
+ }
+
+ @Override
+ public boolean isManageConferenceVisible() {
+ LogUtil.i("VideoCallFragment.isManageConferenceVisible", null);
+ return false;
+ }
+
+ @Override
+ public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ contactGridManager.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public void showNoteSentToast() {
+ LogUtil.i("VideoCallFragment.showNoteSentToast", null);
+ }
+
+ @Override
+ public void updateInCallScreenColors() {
+ LogUtil.i("VideoCallFragment.updateColors", null);
+ }
+
+ @Override
+ public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {
+ LogUtil.i("VideoCallFragment.onInCallScreenDialpadVisibilityChange", null);
+ }
+
+ @Override
+ public int getAnswerAndDialpadContainerResourceId() {
+ return 0;
+ }
+
+ @Override
+ public Fragment getInCallScreenFragment() {
+ return this;
+ }
+
+ @Override
+ public boolean isShowingLocationUi() {
+ return false;
+ }
+
+ @Override
+ public void showLocationUi(Fragment locationUi) {
+ LogUtil.e("VideoCallFragment.showLocationUi", "Emergency video calling not supported");
+ // Do nothing
+ }
+
+ private boolean isLandscape() {
+ // Choose orientation based on display orientation, not window orientation
+ int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
+ return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
+ }
+
+ private void enterGreenScreenMode() {
+ LogUtil.i("VideoCallFragment.enterGreenScreenMode", null);
+ RelativeLayout.LayoutParams params =
+ new RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
+ params.addRule(RelativeLayout.ALIGN_PARENT_START);
+ params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+ previewSurfaceView.setLayoutParams(params);
+ previewSurfaceView.setOutlineProvider(null);
+ updateOverlayBackground();
+ contactGridManager.setIsMiddleRowVisible(true);
+ updateMutePreviewOverlayVisibility();
+
+ previewOffBlurredImageView.setLayoutParams(params);
+ previewOffBlurredImageView.setOutlineProvider(null);
+ previewOffBlurredImageView.setClipToOutline(false);
+ }
+
+ private void exitGreenScreenMode() {
+ LogUtil.i("VideoCallFragment.exitGreenScreenMode", null);
+ Resources resources = getResources();
+ RelativeLayout.LayoutParams params =
+ new RelativeLayout.LayoutParams(
+ (int) resources.getDimension(R.dimen.videocall_preview_width),
+ (int) resources.getDimension(R.dimen.videocall_preview_height));
+ params.setMargins(
+ 0, 0, 0, (int) resources.getDimension(R.dimen.videocall_preview_margin_bottom));
+ if (isLandscape()) {
+ params.addRule(RelativeLayout.ALIGN_PARENT_END);
+ params.setMarginEnd((int) resources.getDimension(R.dimen.videocall_preview_margin_end));
+ } else {
+ params.addRule(RelativeLayout.ALIGN_PARENT_START);
+ params.setMarginStart((int) resources.getDimension(R.dimen.videocall_preview_margin_start));
+ }
+ params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ previewSurfaceView.setLayoutParams(params);
+ updateOverlayBackground();
+ contactGridManager.setIsMiddleRowVisible(false);
+ updateMutePreviewOverlayVisibility();
+
+ previewOffBlurredImageView.setLayoutParams(params);
+ previewOffBlurredImageView.setClipToOutline(true);
+ }
+
+ private void updateVideoOffViews() {
+ // Always hide the preview off and remote off views in green screen mode.
+ boolean previewEnabled = isInGreenScreenMode || shouldShowPreview;
+ previewOffOverlay.setVisibility(previewEnabled ? View.GONE : View.VISIBLE);
+
+ boolean remoteEnabled = isInGreenScreenMode || shouldShowRemote;
+ boolean isResumed = remoteEnabled && !isRemotelyHeld;
+ if (isResumed) {
+ boolean wasRemoteVideoOff =
+ TextUtils.equals(
+ remoteVideoOff.getText(),
+ remoteVideoOff.getResources().getString(R.string.videocall_remote_video_off));
+ // The text needs to be updated and hidden after enough delay in order to be announced by
+ // talkback.
+ remoteVideoOff.setText(
+ wasRemoteVideoOff
+ ? R.string.videocall_remote_video_on
+ : R.string.videocall_remotely_resumed);
+ remoteVideoOff.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ remoteVideoOff.setVisibility(View.GONE);
+ }
+ },
+ VIDEO_OFF_VIEW_FADE_OUT_DELAY_IN_MILLIS);
+ } else {
+ remoteVideoOff.setText(
+ isRemotelyHeld ? R.string.videocall_remotely_held : R.string.videocall_remote_video_off);
+ remoteVideoOff.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void updateOverlayBackground() {
+ if (isInGreenScreenMode) {
+ // We want to darken the preview view to make text and buttons readable. The fullscreen
+ // background is below the preview view so use the green screen background instead.
+ animateSetVisibility(greenScreenBackgroundView, View.VISIBLE);
+ animateSetVisibility(fullscreenBackgroundView, View.GONE);
+ } else if (!isInFullscreenMode) {
+ // We want to darken the remote view to make text and buttons readable. The green screen
+ // background is above the preview view so it would darken the preview too. Use the fullscreen
+ // background instead.
+ animateSetVisibility(greenScreenBackgroundView, View.GONE);
+ animateSetVisibility(fullscreenBackgroundView, View.VISIBLE);
+ } else {
+ animateSetVisibility(greenScreenBackgroundView, View.GONE);
+ animateSetVisibility(fullscreenBackgroundView, View.GONE);
+ }
+ }
+
+ private void updateMutePreviewOverlayVisibility() {
+ // Normally the mute overlay shows on the bottom right of the preview bubble. In green screen
+ // mode the preview is fullscreen so there's no where to anchor it.
+ mutePreviewOverlay.setVisibility(
+ muteButton.isChecked() && !isInGreenScreenMode ? View.VISIBLE : View.GONE);
+ }
+
+ private static void animateSetVisibility(final View view, final int visibility) {
+ if (view.getVisibility() == visibility) {
+ return;
+ }
+
+ int startAlpha;
+ int endAlpha;
+ if (visibility == View.GONE) {
+ startAlpha = 1;
+ endAlpha = 0;
+ } else if (visibility == View.VISIBLE) {
+ startAlpha = 0;
+ endAlpha = 1;
+ } else {
+ Assert.fail();
+ return;
+ }
+
+ view.setAlpha(startAlpha);
+ view.setVisibility(View.VISIBLE);
+ view.animate()
+ .alpha(endAlpha)
+ .withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ view.setVisibility(visibility);
+ }
+ })
+ .start();
+ }
+
+ @Override
+ public void onSystemUiVisibilityChange(int visibility) {
+ boolean navBarVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+ videoCallScreenDelegate.onSystemUiVisibilityChange(navBarVisible);
+ }
+
+ protected void onCameraPermissionGranted() {
+ videoCallScreenDelegate.onCameraPermissionGranted();
+ }
+
+ private void checkCameraPermission() {
+ // Checks if user has consent of camera permission and the permission is granted.
+ // If camera permission is revoked, shows system permission dialog.
+ // If camera permission is granted but user doesn't have consent of camera permission
+ // (which means it's first time making video call), shows custom dialog instead. This
+ // will only be shown to user once.
+ if (!VideoUtils.hasCameraPermissionAndAllowedByUser(getContext())) {
+ videoCallScreenDelegate.onCameraPermissionDialogShown();
+ if (!VideoUtils.hasCameraPermission(getContext())) {
+ requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
+ } else {
+ CameraPermissionDialogFragment.newInstance()
+ .show(getChildFragmentManager(), CAMERA_PERMISSION_DIALOG_FRAMENT_TAG);
+ }
+ }
+ }
+}