blob: 28ee774babe733ec04115c6113caad49f383f175 [file] [log] [blame]
/*
* 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.graphics.Point;
import android.graphics.drawable.Animatable;
import android.os.Bundle;
import android.support.annotation.ColorInt;
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.FrameLayout;
import android.widget.ImageButton;
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.dialer.util.PermissionsUtil;
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 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 View controls;
private View controlsContainer;
private SurfaceView previewSurfaceView;
private SurfaceView remoteSurfaceView;
private View greenScreenBackgroundView;
private View fullscreenBackgroundView;
private FrameLayout previewRoot;
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.shouldShowCameraPermissionToast()) {
LogUtil.i(
"SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.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(
"SurfaceViewVideoCallFragment.onRequestPermissionsResult",
"Camera permission granted.");
videoCallScreenDelegate.onCameraPermissionGranted();
} else {
LogUtil.i(
"SurfaceViewVideoCallFragment.onRequestPermissionsResult", "Camera permission denied.");
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Nullable
@Override
public View onCreateView(
LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) {
LogUtil.i("SurfaceViewVideoCallFragment.onCreateView", null);
View view = layoutInflater.inflate(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);
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.setZOrderMediaOverlay(true);
previewOffOverlay.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View v) {
checkCameraPermission();
}
});
remoteSurfaceView = (SurfaceView) view.findViewById(R.id.videocall_video_remote);
remoteSurfaceView.setOnClickListener(
surfaceView -> {
videoCallScreenDelegate.resetAutoFullscreenTimer();
if (isInFullscreenMode) {
updateFullscreenAndGreenScreenMode(
false /* shouldShowFullscreen */, false /* shouldShowGreenScreen */);
} else {
updateFullscreenAndGreenScreenMode(
true /* shouldShowFullscreen */, false /* shouldShowGreenScreen */);
}
});
greenScreenBackgroundView = view.findViewById(R.id.videocall_green_screen_background);
fullscreenBackgroundView = view.findViewById(R.id.videocall_fullscreen_background);
previewRoot = (FrameLayout) view.findViewById(R.id.videocall_preview_root);
// 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("SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.onResume", null);
inCallScreenDelegate.onInCallScreenResumed();
}
@Override
public void onPause() {
super.onPause();
LogUtil.i("SurfaceViewVideoCallFragment.onPause", null);
inCallScreenDelegate.onInCallScreenPaused();
}
@Override
public void onStop() {
super.onStop();
LogUtil.i("SurfaceViewVideoCallFragment.onStop", null);
onVideoScreenStop();
}
@Override
public void onVideoScreenStop() {
getView().removeCallbacks(cameraPermissionDialogRunnable);
videoCallScreenDelegate.onVideoCallScreenUiUnready();
}
private void exitFullscreenMode() {
LogUtil.i("SurfaceViewVideoCallFragment.exitFullscreenMode", null);
if (!getView().isAttachedToWindow()) {
LogUtil.i("SurfaceViewVideoCallFragment.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[] {previewRoot, 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("SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.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.hasCameraPermissionAndShownPrivacyToast(getContext())) {
LogUtil.i("SurfaceViewVideoCallFragment.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(
"SurfaceViewVideoCallFragment.showVideoViews",
"showPreview: %b, shouldShowRemote: %b",
shouldShowPreview,
shouldShowRemote);
this.shouldShowPreview = shouldShowPreview;
this.shouldShowRemote = shouldShowRemote;
this.isRemotelyHeld = isRemotelyHeld;
previewSurfaceView.setVisibility(shouldShowPreview ? View.VISIBLE : View.INVISIBLE);
videoCallScreenDelegate.setSurfaceViews(previewSurfaceView, remoteSurfaceView);
updateVideoOffViews();
}
@Override
public void onLocalVideoDimensionsChanged() {
LogUtil.i("SurfaceViewVideoCallFragment.onLocalVideoDimensionsChanged", null);
}
@Override
public void onLocalVideoOrientationChanged() {
LogUtil.i("SurfaceViewVideoCallFragment.onLocalVideoOrientationChanged", null);
}
/** Called when the remote video's dimensions change. */
@Override
public void onRemoteVideoDimensionsChanged() {
LogUtil.i("SurfaceViewVideoCallFragment.onRemoteVideoDimensionsChanged", null);
}
@Override
public void updateFullscreenAndGreenScreenMode(
boolean shouldShowFullscreen, boolean shouldShowGreenScreen) {
LogUtil.i(
"SurfaceViewVideoCallFragment.updateFullscreenAndGreenScreenMode",
"shouldShowFullscreen: %b, shouldShowGreenScreen: %b",
shouldShowFullscreen,
shouldShowGreenScreen);
if (getActivity() == null) {
LogUtil.i(
"SurfaceViewVideoCallFragment.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(
"SurfaceViewVideoCallFragment.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(
"SurfaceViewVideoCallFragment.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(
"SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.setEnabled", "enabled: " + enabled);
speakerButtonController.setEnabled(enabled);
muteButton.setEnabled(enabled);
cameraOffButton.setEnabled(enabled);
switchOnHoldCallController.setEnabled(enabled);
}
@Override
public void setHold(boolean value) {
LogUtil.i("SurfaceViewVideoCallFragment.setHold", "value: " + value);
}
@Override
public void setCameraSwitched(boolean isBackFacingCamera) {
LogUtil.i(
"SurfaceViewVideoCallFragment.setCameraSwitched",
"isBackFacingCamera: " + isBackFacingCamera);
}
@Override
public void setVideoPaused(boolean isPaused) {
LogUtil.i("SurfaceViewVideoCallFragment.setVideoPaused", "isPaused: " + isPaused);
cameraOffButton.setChecked(isPaused);
}
@Override
public void setAudioState(CallAudioState audioState) {
LogUtil.i("SurfaceViewVideoCallFragment.setAudioState", "audioState: " + audioState);
speakerButtonController.setAudioState(audioState);
muteButton.setChecked(audioState.isMuted());
updateMutePreviewOverlayVisibility();
}
@Override
public void updateButtonStates() {
LogUtil.i("SurfaceViewVideoCallFragment.updateButtonState", null);
speakerButtonController.updateButtonState();
switchOnHoldCallController.updateButtonState();
}
@Override
public void updateInCallButtonUiColors(@ColorInt int color) {}
@Override
public Fragment getInCallButtonUiFragment() {
return this;
}
@Override
public void showAudioRouteSelector() {
LogUtil.i("SurfaceViewVideoCallFragment.showAudioRouteSelector", null);
AudioRouteSelectorDialogFragment.newInstance(inCallButtonUiDelegate.getCurrentAudioState())
.show(getChildFragmentManager(), null);
}
@Override
public void onAudioRouteSelected(int audioRoute) {
LogUtil.i("SurfaceViewVideoCallFragment.onAudioRouteSelected", "audioRoute: " + audioRoute);
inCallButtonUiDelegate.setAudioRoute(audioRoute);
}
@Override
public void onAudioRouteSelectorDismiss() {}
@Override
public void setPrimary(@NonNull PrimaryInfo primaryInfo) {
LogUtil.i("SurfaceViewVideoCallFragment.setPrimary", primaryInfo.toString());
contactGridManager.setPrimary(primaryInfo);
}
@Override
public void setSecondary(@NonNull SecondaryInfo secondaryInfo) {
LogUtil.i("SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.setCallState", primaryCallState.toString());
contactGridManager.setCallState(primaryCallState);
}
@Override
public void setEndCallButtonEnabled(boolean enabled, boolean animate) {
LogUtil.i("SurfaceViewVideoCallFragment.setEndCallButtonEnabled", "enabled: " + enabled);
}
@Override
public void showManageConferenceCallButton(boolean visible) {
LogUtil.i("SurfaceViewVideoCallFragment.showManageConferenceCallButton", "visible: " + visible);
}
@Override
public boolean isManageConferenceVisible() {
LogUtil.i("SurfaceViewVideoCallFragment.isManageConferenceVisible", null);
return false;
}
@Override
public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
contactGridManager.dispatchPopulateAccessibilityEvent(event);
}
@Override
public void showNoteSentToast() {
LogUtil.i("SurfaceViewVideoCallFragment.showNoteSentToast", null);
}
@Override
public void updateInCallScreenColors() {
LogUtil.i("SurfaceViewVideoCallFragment.updateColors", null);
}
@Override
public void onInCallScreenDialpadVisibilityChange(boolean isShowing) {
LogUtil.i("SurfaceViewVideoCallFragment.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(
"SurfaceViewVideoCallFragment.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("SurfaceViewVideoCallFragment.enterGreenScreenMode", null);
updateOverlayBackground();
contactGridManager.setIsMiddleRowVisible(true);
updateMutePreviewOverlayVisibility();
}
private void exitGreenScreenMode() {
LogUtil.i("SurfaceViewVideoCallFragment.exitGreenScreenMode", null);
updateOverlayBackground();
contactGridManager.setIsMiddleRowVisible(false);
updateMutePreviewOverlayVisibility();
}
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);
if (navBarVisible) {
updateFullscreenAndGreenScreenMode(
false /* shouldShowFullscreen */, false /* shouldShowGreenScreen */);
} else {
updateFullscreenAndGreenScreenMode(
true /* shouldShowFullscreen */, false /* shouldShowGreenScreen */);
}
}
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.hasCameraPermissionAndShownPrivacyToast(getContext())) {
videoCallScreenDelegate.onCameraPermissionDialogShown();
if (!VideoUtils.hasCameraPermission(getContext())) {
requestPermissions(new String[] {permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
} else {
PermissionsUtil.showCameraPermissionToast(getContext());
videoCallScreenDelegate.onCameraPermissionGranted();
}
}
}
}