| /* |
| * 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(); |
| } |
| } |
| } |
| } |