1065 lines
38 KiB
Java
1065 lines
38 KiB
Java
/*
|
|
* 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.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(getActivity().isInMultiWindowMode() ? 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(getActivity().isInMultiWindowMode() ? 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() {
|
|
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 (getActivity().isInMultiWindowMode()) {
|
|
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() && !getActivity().isInMultiWindowMode()) {
|
|
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 onHandoverFromWiFiToLte() {}
|
|
|
|
@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();
|
|
}
|
|
}
|
|
}
|
|
}
|