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