| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * Copyright (C) 2023 The LineageOS 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; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.telecom.InCallService.VideoCall; |
| import android.telecom.VideoProfile; |
| import android.telecom.VideoProfile.CameraCapabilities; |
| import android.view.Surface; |
| import android.view.SurfaceView; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import com.android.dialer.R; |
| import com.android.dialer.common.Assert; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.util.PermissionsUtil; |
| import com.android.incallui.InCallPresenter.InCallDetailsListener; |
| import com.android.incallui.InCallPresenter.InCallOrientationListener; |
| import com.android.incallui.InCallPresenter.InCallStateListener; |
| import com.android.incallui.InCallPresenter.IncomingCallListener; |
| import com.android.incallui.call.CallList; |
| import com.android.incallui.call.DialerCall; |
| import com.android.incallui.call.DialerCall.CameraDirection; |
| import com.android.incallui.call.InCallVideoCallCallbackNotifier; |
| import com.android.incallui.call.InCallVideoCallCallbackNotifier.SurfaceChangeListener; |
| import com.android.incallui.call.state.DialerCallState; |
| import com.android.incallui.util.AccessibilityUtil; |
| import com.android.incallui.video.protocol.VideoCallScreen; |
| import com.android.incallui.video.protocol.VideoCallScreenDelegate; |
| import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate; |
| import com.android.incallui.videosurface.protocol.VideoSurfaceTexture; |
| import com.android.incallui.videotech.utils.SessionModificationState; |
| import com.android.incallui.videotech.utils.VideoUtils; |
| |
| import java.util.Objects; |
| |
| /** |
| * Logic related to the {@link VideoCallScreen} and for managing changes to the video calling |
| * surfaces based on other user interface events and incoming events from the {@class |
| * VideoCallListener}. |
| * |
| * <p>When a call's video state changes to bi-directional video, the {@link |
| * com.android.incallui.VideoCallPresenter} performs the following negotiation with the telephony |
| * layer: |
| * |
| * <ul> |
| * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface. |
| * <li>{@code VideoCallPresenter} creates the preview surface. |
| * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera. |
| * <li>Telephony layer sends {@link CameraCapabilities}, including the dimensions of the video for |
| * the current camera. |
| * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect ratio of |
| * the camera. |
| * <li>{@code VideoCallPresenter} informs telephony of the new preview surface. |
| * </ul> |
| * |
| * <p>When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both |
| * surfaces. |
| */ |
| public class VideoCallPresenter |
| implements IncomingCallListener, |
| InCallOrientationListener, |
| InCallStateListener, |
| InCallDetailsListener, |
| SurfaceChangeListener, |
| InCallPresenter.InCallEventListener, |
| VideoCallScreenDelegate, |
| CallList.Listener { |
| |
| private static boolean isVideoMode = false; |
| |
| private final Handler handler = new Handler(Looper.getMainLooper()); |
| private VideoCallScreen videoCallScreen; |
| |
| /** The current context. */ |
| private Context context; |
| |
| /** The call the video surfaces are currently related to */ |
| private DialerCall primaryCall; |
| /** |
| * The {@link VideoCall} used to inform the video telephony layer of changes to the video |
| * surfaces. |
| */ |
| private VideoCall videoCall; |
| /** Determines if the current UI state represents a video call. */ |
| private int currentVideoState; |
| /** DialerCall's current state */ |
| private int currentCallState = DialerCallState.INVALID; |
| /** Determines the device orientation (portrait/lanscape). */ |
| private int deviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN; |
| /** Tracks the state of the preview surface negotiation with the telephony layer. */ |
| private int previewSurfaceState = PreviewSurfaceState.NONE; |
| /** |
| * Determines whether video calls should automatically enter full screen mode after {@link |
| * #autoFullscreenTimeoutMillis} milliseconds. |
| */ |
| private boolean isAutoFullscreenEnabled = false; |
| /** |
| * Determines the number of milliseconds after which a video call will automatically enter |
| * fullscreen mode. Requires {@link #isAutoFullscreenEnabled} to be {@code true}. |
| */ |
| private int autoFullscreenTimeoutMillis = 0; |
| /** |
| * Determines if the countdown is currently running to automatically enter full screen video mode. |
| */ |
| private boolean autoFullScreenPending = false; |
| /** Whether if the call is remotely held. */ |
| private boolean isRemotelyHeld = false; |
| /** |
| * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto |
| * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit the |
| * dialpad). |
| */ |
| private final Runnable autoFullscreenRunnable = () -> { |
| if (autoFullScreenPending |
| && !InCallPresenter.getInstance().isDialpadVisible() |
| && isVideoMode) { |
| |
| LogUtil.v("VideoCallPresenter.mAutoFullScreenRunnable", "entering fullscreen mode"); |
| InCallPresenter.getInstance().setFullScreen(true); |
| autoFullScreenPending = false; |
| } else { |
| LogUtil.v( |
| "VideoCallPresenter.mAutoFullScreenRunnable", |
| "skipping scheduled fullscreen mode."); |
| } |
| }; |
| |
| private boolean isVideoCallScreenUiReady; |
| |
| private static boolean isCameraRequired(int videoState, int sessionModificationState) { |
| return VideoProfile.isBidirectional(videoState) |
| || VideoProfile.isTransmissionEnabled(videoState) |
| || isVideoUpgrade(sessionModificationState); |
| } |
| |
| /** |
| * Determines if the incoming video surface should be shown based on the current videoState and |
| * callState. The video surface is shown when incoming video is not paused, the call is active or |
| * dialing and video reception is enabled. |
| * |
| * @param videoState The current video state. |
| * @param callState The current call state. |
| * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise. |
| */ |
| static boolean showIncomingVideo(int videoState, int callState) { |
| |
| boolean isPaused = VideoProfile.isPaused(videoState); |
| boolean isCallActive = callState == DialerCallState.ACTIVE; |
| // Show incoming Video for dialing calls to support early media |
| boolean isCallOutgoingPending = |
| DialerCallState.isDialing(callState) || callState == DialerCallState.CONNECTING; |
| |
| return !isPaused |
| && (isCallActive || isCallOutgoingPending) |
| && VideoProfile.isReceptionEnabled(videoState); |
| } |
| |
| /** |
| * Determines if the outgoing video surface should be shown based on the current videoState. The |
| * video surface is shown if video transmission is enabled. |
| * |
| * @return {@code true} if the the outgoing video surface should be shown, {@code false} |
| * otherwise. |
| */ |
| private static boolean showOutgoingVideo( |
| Context context, int videoState, int sessionModificationState) { |
| if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(context)) { |
| LogUtil.i("VideoCallPresenter.showOutgoingVideo", "Camera permission is disabled by user."); |
| return false; |
| } |
| |
| return VideoProfile.isTransmissionEnabled(videoState) |
| || isVideoUpgrade(sessionModificationState); |
| } |
| |
| private static void updateCameraSelection(DialerCall call) { |
| LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + call); |
| LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + toSimpleString(call)); |
| |
| final DialerCall activeCall = CallList.getInstance().getActiveCall(); |
| int cameraDir; |
| |
| // this function should never be called with null call object, however if it happens we |
| // should handle it gracefully. |
| if (call == null) { |
| cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN; |
| LogUtil.e( |
| "VideoCallPresenter.updateCameraSelection", |
| "call is null. Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)"); |
| } |
| |
| // Clear camera direction if this is not a video call. |
| else if (isAudioCall(call) && !isVideoUpgrade(call)) { |
| cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN; |
| call.setCameraDir(cameraDir); |
| } |
| |
| // If this is a waiting video call, default to active call's camera, |
| // since we don't want to change the current camera for waiting call |
| // without user's permission. |
| else if (isVideoCall(activeCall) && isIncomingVideoCall(call)) { |
| cameraDir = activeCall.getCameraDir(); |
| } |
| |
| // Infer the camera direction from the video state and store it, |
| // if this is an outgoing video call. |
| else if (isOutgoingVideoCall(call) && !isCameraDirectionSet(call)) { |
| cameraDir = toCameraDirection(call.getVideoState()); |
| call.setCameraDir(cameraDir); |
| } |
| |
| // Use the stored camera dir if this is an outgoing video call for which camera direction |
| // is set. |
| else if (isOutgoingVideoCall(call)) { |
| cameraDir = call.getCameraDir(); |
| } |
| |
| // Infer the camera direction from the video state and store it, |
| // if this is an active video call and camera direction is not set. |
| else if (isActiveVideoCall(call) && !isCameraDirectionSet(call)) { |
| cameraDir = toCameraDirection(call.getVideoState()); |
| call.setCameraDir(cameraDir); |
| } |
| |
| // Use the stored camera dir if this is an active video call for which camera direction |
| // is set. |
| else if (isActiveVideoCall(call)) { |
| cameraDir = call.getCameraDir(); |
| } |
| |
| // For all other cases infer the camera direction but don't store it in the call object. |
| else { |
| cameraDir = toCameraDirection(call.getVideoState()); |
| } |
| |
| LogUtil.i( |
| "VideoCallPresenter.updateCameraSelection", |
| "setting camera direction to %d, call: %s", |
| cameraDir, |
| call); |
| final InCallCameraManager cameraManager = |
| InCallPresenter.getInstance().getInCallCameraManager(); |
| cameraManager.setUseFrontFacingCamera( |
| cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING); |
| } |
| |
| private static int toCameraDirection(int videoState) { |
| return VideoProfile.isTransmissionEnabled(videoState) |
| && !VideoProfile.isBidirectional(videoState) |
| ? CameraDirection.CAMERA_DIRECTION_BACK_FACING |
| : CameraDirection.CAMERA_DIRECTION_FRONT_FACING; |
| } |
| |
| private static boolean isCameraDirectionSet(DialerCall call) { |
| return isVideoCall(call) && call.getCameraDir() != CameraDirection.CAMERA_DIRECTION_UNKNOWN; |
| } |
| |
| private static String toSimpleString(DialerCall call) { |
| return call == null ? null : call.toSimpleString(); |
| } |
| |
| /** |
| * Initializes the presenter. |
| * |
| * @param context The current context. |
| */ |
| @Override |
| public void initVideoCallScreenDelegate(Context context, VideoCallScreen videoCallScreen) { |
| this.context = context; |
| this.videoCallScreen = videoCallScreen; |
| isAutoFullscreenEnabled = |
| this.context.getResources().getBoolean(R.bool.video_call_auto_fullscreen); |
| autoFullscreenTimeoutMillis = |
| this.context.getResources().getInteger(R.integer.video_call_auto_fullscreen_timeout); |
| } |
| |
| /** Called when the user interface is ready to be used. */ |
| @Override |
| public void onVideoCallScreenUiReady() { |
| LogUtil.v("VideoCallPresenter.onVideoCallScreenUiReady", ""); |
| Assert.checkState(!isVideoCallScreenUiReady); |
| |
| deviceOrientation = InCallOrientationEventListener.getCurrentOrientation(); |
| |
| // Register for call state changes last |
| InCallPresenter.getInstance().addListener(this); |
| InCallPresenter.getInstance().addDetailsListener(this); |
| InCallPresenter.getInstance().addIncomingCallListener(this); |
| InCallPresenter.getInstance().addOrientationListener(this); |
| // To get updates of video call details changes |
| InCallPresenter.getInstance().addInCallEventListener(this); |
| InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate()); |
| InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate()); |
| |
| CallList.getInstance().addListener(this); |
| |
| // Register for surface and video events from {@link InCallVideoCallListener}s. |
| InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this); |
| currentVideoState = VideoProfile.STATE_AUDIO_ONLY; |
| currentCallState = DialerCallState.INVALID; |
| |
| InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState(); |
| onStateChange(inCallState, inCallState, CallList.getInstance()); |
| isVideoCallScreenUiReady = true; |
| |
| Point sourceVideoDimensions = getRemoteVideoSurfaceTexture().getSourceVideoDimensions(); |
| if (sourceVideoDimensions != null && primaryCall != null) { |
| int width = primaryCall.getPeerDimensionWidth(); |
| int height = primaryCall.getPeerDimensionHeight(); |
| boolean updated = DialerCall.UNKNOWN_PEER_DIMENSIONS != width |
| && DialerCall.UNKNOWN_PEER_DIMENSIONS != height; |
| if (updated && (sourceVideoDimensions.x != width || sourceVideoDimensions.y != height)) { |
| onUpdatePeerDimensions(primaryCall, width, height); |
| } |
| } |
| } |
| |
| /** Called when the user interface is no longer ready to be used. */ |
| @Override |
| public void onVideoCallScreenUiUnready() { |
| LogUtil.v("VideoCallPresenter.onVideoCallScreenUiUnready", ""); |
| Assert.checkState(isVideoCallScreenUiReady); |
| |
| cancelAutoFullScreen(); |
| |
| InCallPresenter.getInstance().removeListener(this); |
| InCallPresenter.getInstance().removeDetailsListener(this); |
| InCallPresenter.getInstance().removeIncomingCallListener(this); |
| InCallPresenter.getInstance().removeOrientationListener(this); |
| InCallPresenter.getInstance().removeInCallEventListener(this); |
| InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null); |
| |
| CallList.getInstance().removeListener(this); |
| |
| InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this); |
| |
| // Ensure that the call's camera direction is updated (most likely to UNKNOWN). Normally this |
| // happens after any call state changes but we're unregistering from InCallPresenter above so |
| // we won't get any more call state changes. See a bug. |
| if (primaryCall != null) { |
| updateCameraSelection(primaryCall); |
| } |
| |
| isVideoCallScreenUiReady = false; |
| } |
| |
| /** |
| * Handles clicks on the video surfaces. If not currently in fullscreen mode, will set fullscreen. |
| */ |
| private void onSurfaceClick() { |
| LogUtil.i("VideoCallPresenter.onSurfaceClick", ""); |
| cancelAutoFullScreen(); |
| if (!InCallPresenter.getInstance().isFullscreen()) { |
| InCallPresenter.getInstance().setFullScreen(true); |
| } else { |
| InCallPresenter.getInstance().setFullScreen(false); |
| maybeAutoEnterFullscreen(primaryCall); |
| // If Activity is not multiwindow, fullscreen will be driven by SystemUI visibility changes |
| // instead. See #onSystemUiVisibilityChange(boolean) |
| |
| // TODO (keyboardr): onSystemUiVisibilityChange isn't being called the first time |
| // visibility changes after orientation change, so this is currently always done as a backup. |
| } |
| } |
| |
| @Override |
| public void onSystemUiVisibilityChange(boolean visible) { |
| // If the SystemUI has changed to be visible, take us out of fullscreen mode |
| LogUtil.i("VideoCallPresenter.onSystemUiVisibilityChange", "visible: " + visible); |
| if (visible) { |
| InCallPresenter.getInstance().setFullScreen(false); |
| maybeAutoEnterFullscreen(primaryCall); |
| } |
| } |
| |
| @Override |
| public VideoSurfaceTexture getLocalVideoSurfaceTexture() { |
| return InCallPresenter.getInstance().getLocalVideoSurfaceTexture(); |
| } |
| |
| @Override |
| public VideoSurfaceTexture getRemoteVideoSurfaceTexture() { |
| return InCallPresenter.getInstance().getRemoteVideoSurfaceTexture(); |
| } |
| |
| @Override |
| public void setSurfaceViews(SurfaceView preview, SurfaceView remote) { |
| throw Assert.createUnsupportedOperationFailException(); |
| } |
| |
| @Override |
| public int getDeviceOrientation() { |
| return deviceOrientation; |
| } |
| |
| /** |
| * This should only be called when user approved the camera permission, which is local action and |
| * does NOT change any call states. |
| */ |
| @Override |
| public void onCameraPermissionGranted() { |
| LogUtil.i("VideoCallPresenter.onCameraPermissionGranted", ""); |
| PermissionsUtil.setCameraPrivacyToastShown(context); |
| enableCamera(primaryCall, isCameraRequired()); |
| showVideoUi( |
| primaryCall.getVideoState(), |
| primaryCall.getState(), |
| primaryCall.getVideoTech().getSessionModificationState(), |
| primaryCall.isRemotelyHeld()); |
| InCallPresenter.getInstance().getInCallCameraManager().onCameraPermissionGranted(); |
| } |
| |
| @Override |
| public boolean isFullscreen() { |
| return InCallPresenter.getInstance().isFullscreen(); |
| } |
| |
| /** |
| * Called when the user interacts with the UI. If a fullscreen timer is pending then we start the |
| * timer from scratch to avoid having the UI disappear while the user is interacting with it. |
| */ |
| @Override |
| public void resetAutoFullscreenTimer() { |
| if (autoFullScreenPending) { |
| LogUtil.i("VideoCallPresenter.resetAutoFullscreenTimer", "resetting"); |
| handler.removeCallbacks(autoFullscreenRunnable); |
| handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis); |
| } |
| } |
| |
| /** |
| * Handles incoming calls. |
| * |
| * @param oldState The old in call state. |
| * @param newState The new in call state. |
| * @param call The call. |
| */ |
| @Override |
| public void onIncomingCall( |
| InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, DialerCall call) { |
| // If video call screen ui is already destroyed, this shouldn't be called. But the UI may be |
| // updated synchronized by {@link CallCardPresenter#onIncomingCall} before this is called, this |
| // could still be called. Thus just do nothing in this case. |
| if (!isVideoCallScreenUiReady) { |
| LogUtil.i("VideoCallPresenter.onIncomingCall", "UI is not ready"); |
| return; |
| } |
| // same logic should happen as with onStateChange() |
| onStateChange(oldState, newState, CallList.getInstance()); |
| } |
| |
| /** |
| * Handles state changes (including incoming calls) |
| * |
| * @param newState The in call state. |
| * @param callList The call list. |
| */ |
| @Override |
| public void onStateChange( |
| InCallPresenter.InCallState oldState, |
| InCallPresenter.InCallState newState, |
| CallList callList) { |
| LogUtil.v( |
| "VideoCallPresenter.onStateChange", |
| "oldState: %s, newState: %s, isVideoMode: %b", |
| oldState, |
| newState, |
| isVideoMode()); |
| |
| if (newState == InCallPresenter.InCallState.NO_CALLS) { |
| if (isVideoMode()) { |
| exitVideoMode(); |
| } |
| |
| InCallPresenter.getInstance().cleanupSurfaces(); |
| } |
| |
| // Determine the primary active call). |
| DialerCall primary = null; |
| |
| // Determine the call which is the focus of the user's attention. In the case of an |
| // incoming call waiting call, the primary call is still the active video call, however |
| // the determination of whether we should be in fullscreen mode is based on the type of the |
| // incoming call, not the active video call. |
| DialerCall currentCall = null; |
| |
| if (newState == InCallPresenter.InCallState.INCOMING) { |
| // We don't want to replace active video call (primary call) |
| // with a waiting call, since user may choose to ignore/decline the waiting call and |
| // this should have no impact on current active video call, that is, we should not |
| // change the camera or UI unless the waiting VT call becomes active. |
| primary = callList.getActiveCall(); |
| currentCall = callList.getIncomingCall(); |
| if (!isActiveVideoCall(primary)) { |
| primary = callList.getIncomingCall(); |
| } |
| } else if (newState == InCallPresenter.InCallState.OUTGOING) { |
| currentCall = primary = callList.getOutgoingCall(); |
| } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) { |
| currentCall = primary = callList.getPendingOutgoingCall(); |
| } else if (newState == InCallPresenter.InCallState.INCALL) { |
| currentCall = primary = callList.getActiveCall(); |
| } |
| |
| final boolean primaryChanged = !Objects.equals(primaryCall, primary); |
| LogUtil.i( |
| "VideoCallPresenter.onStateChange", |
| "primaryChanged: %b, primary: %s, mPrimaryCall: %s", |
| primaryChanged, |
| primary, |
| primaryCall); |
| if (primaryChanged) { |
| onPrimaryCallChanged(primary); |
| } else if (primaryCall != null) { |
| updateVideoCall(primary); |
| } |
| updateCallCache(primary); |
| |
| // If the call context changed, potentially exit fullscreen or schedule auto enter of |
| // fullscreen mode. |
| // If the current call context is no longer a video call, exit fullscreen mode. |
| maybeExitFullscreen(currentCall); |
| // Schedule auto-enter of fullscreen mode if the current call context is a video call |
| maybeAutoEnterFullscreen(currentCall); |
| } |
| |
| /** |
| * Handles a change to the fullscreen mode of the app. |
| * |
| * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise. |
| */ |
| @Override |
| public void onFullscreenModeChanged(boolean isFullscreenMode) { |
| cancelAutoFullScreen(); |
| if (primaryCall != null) { |
| updateFullscreenAndGreenScreenMode( |
| primaryCall.getState(), primaryCall.getVideoTech().getSessionModificationState()); |
| } else { |
| updateFullscreenAndGreenScreenMode( |
| DialerCallState.INVALID, SessionModificationState.NO_REQUEST); |
| } |
| } |
| |
| private void checkForVideoStateChange(DialerCall call) { |
| final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call); |
| final boolean hasVideoStateChanged = currentVideoState != call.getVideoState(); |
| |
| LogUtil.v( |
| "VideoCallPresenter.checkForVideoStateChange", |
| "shouldShowVideoUi: %b, hasVideoStateChanged: %b, isVideoMode: %b, previousVideoState: %s," |
| + " newVideoState: %s", |
| shouldShowVideoUi, |
| hasVideoStateChanged, |
| isVideoMode(), |
| VideoProfile.videoStateToString(currentVideoState), |
| VideoProfile.videoStateToString(call.getVideoState())); |
| if (!hasVideoStateChanged) { |
| return; |
| } |
| |
| updateCameraSelection(call); |
| |
| if (shouldShowVideoUi) { |
| adjustVideoMode(call); |
| } else if (isVideoMode()) { |
| exitVideoMode(); |
| } |
| } |
| |
| private void checkForCallStateChange(DialerCall call) { |
| final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call); |
| final boolean hasCallStateChanged = |
| currentCallState != call.getState() || isRemotelyHeld != call.isRemotelyHeld(); |
| isRemotelyHeld = call.isRemotelyHeld(); |
| |
| LogUtil.v( |
| "VideoCallPresenter.checkForCallStateChange", |
| "shouldShowVideoUi: %b, hasCallStateChanged: %b, isVideoMode: %b", |
| shouldShowVideoUi, |
| hasCallStateChanged, |
| isVideoMode()); |
| |
| if (!hasCallStateChanged) { |
| return; |
| } |
| |
| if (shouldShowVideoUi) { |
| final InCallCameraManager cameraManager = |
| InCallPresenter.getInstance().getInCallCameraManager(); |
| |
| String prevCameraId = cameraManager.getActiveCameraId(); |
| updateCameraSelection(call); |
| String newCameraId = cameraManager.getActiveCameraId(); |
| |
| if (!Objects.equals(prevCameraId, newCameraId) && isActiveVideoCall(call)) { |
| enableCamera(call, true); |
| } |
| } |
| |
| // Make sure we hide or show the video UI if needed. |
| showVideoUi( |
| call.getVideoState(), |
| call.getState(), |
| call.getVideoTech().getSessionModificationState(), |
| call.isRemotelyHeld()); |
| } |
| |
| private void onPrimaryCallChanged(DialerCall newPrimaryCall) { |
| final boolean shouldShowVideoUi = shouldShowVideoUiForCall(newPrimaryCall); |
| final boolean isVideoMode = isVideoMode(); |
| |
| LogUtil.v( |
| "VideoCallPresenter.onPrimaryCallChanged", |
| "shouldShowVideoUi: %b, isVideoMode: %b", |
| shouldShowVideoUi, |
| isVideoMode); |
| |
| if (!shouldShowVideoUi && isVideoMode) { |
| // Terminate video mode if new primary call is not a video call |
| // and we are currently in video mode. |
| LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "exiting video mode..."); |
| exitVideoMode(); |
| } else if (shouldShowVideoUi) { |
| LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "entering video mode..."); |
| |
| updateCameraSelection(newPrimaryCall); |
| adjustVideoMode(newPrimaryCall); |
| } |
| checkForOrientationAllowedChange(newPrimaryCall); |
| } |
| |
| private boolean isVideoMode() { |
| return isVideoMode; |
| } |
| |
| private void updateCallCache(DialerCall call) { |
| if (call == null) { |
| currentVideoState = VideoProfile.STATE_AUDIO_ONLY; |
| currentCallState = DialerCallState.INVALID; |
| videoCall = null; |
| primaryCall = null; |
| } else { |
| currentVideoState = call.getVideoState(); |
| videoCall = call.getVideoCall(); |
| currentCallState = call.getState(); |
| primaryCall = call; |
| } |
| } |
| |
| /** |
| * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in |
| * changes to the video state. |
| * |
| * @param call The call for which the details changed. |
| * @param details The new call details. |
| */ |
| @Override |
| public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) { |
| LogUtil.v( |
| "VideoCallPresenter.onDetailsChanged", |
| "call: %s, details: %s, mPrimaryCall: %s", |
| call, |
| details, |
| primaryCall); |
| if (call == null) { |
| return; |
| } |
| // If the details change is not for the currently active call no update is required. |
| if (!call.equals(primaryCall)) { |
| LogUtil.v("VideoCallPresenter.onDetailsChanged", "details not for current active call"); |
| return; |
| } |
| |
| updateVideoCall(call); |
| |
| updateCallCache(call); |
| } |
| |
| private void updateVideoCall(DialerCall call) { |
| checkForVideoCallChange(call); |
| checkForVideoStateChange(call); |
| checkForCallStateChange(call); |
| checkForOrientationAllowedChange(call); |
| updateFullscreenAndGreenScreenMode( |
| call.getState(), call.getVideoTech().getSessionModificationState()); |
| } |
| |
| private void checkForOrientationAllowedChange(@Nullable DialerCall call) { |
| // Call could be null when video call ended. This check could prevent unwanted orientation |
| // change before incall UI gets destroyed. |
| if (call != null) { |
| InCallPresenter.getInstance() |
| .setInCallAllowsOrientationChange(isVideoCall(call) || isVideoUpgrade(call)); |
| } |
| } |
| |
| private void updateFullscreenAndGreenScreenMode( |
| int callState, @SessionModificationState int sessionModificationState) { |
| if (videoCallScreen != null) { |
| boolean shouldShowFullscreen = InCallPresenter.getInstance().isFullscreen(); |
| boolean shouldShowGreenScreen = |
| callState == DialerCallState.DIALING |
| || callState == DialerCallState.CONNECTING |
| || callState == DialerCallState.INCOMING |
| || isVideoUpgrade(sessionModificationState); |
| videoCallScreen.updateFullscreenAndGreenScreenMode( |
| shouldShowFullscreen, shouldShowGreenScreen); |
| } |
| } |
| |
| /** Checks for a change to the video call and changes it if required. */ |
| private void checkForVideoCallChange(DialerCall call) { |
| final VideoCall videoCall = call.getVideoCall(); |
| LogUtil.v( |
| "VideoCallPresenter.checkForVideoCallChange", |
| "videoCall: %s, mVideoCall: %s", |
| videoCall, |
| this.videoCall); |
| if (!Objects.equals(videoCall, this.videoCall)) { |
| changeVideoCall(call); |
| } |
| } |
| |
| /** |
| * Handles a change to the video call. Sets the surfaces on the previous call to null and sets the |
| * surfaces on the new video call accordingly. |
| * |
| * @param call The new video call. |
| */ |
| private void changeVideoCall(DialerCall call) { |
| final VideoCall videoCall = call == null ? null : call.getVideoCall(); |
| LogUtil.i( |
| "VideoCallPresenter.changeVideoCall", |
| "videoCall: %s, mVideoCall: %s", |
| videoCall, |
| this.videoCall); |
| final boolean hasChanged = this.videoCall == null && videoCall != null; |
| |
| this.videoCall = videoCall; |
| if (this.videoCall == null) { |
| LogUtil.v("VideoCallPresenter.changeVideoCall", "video call or primary call is null. Return"); |
| return; |
| } |
| |
| if (shouldShowVideoUiForCall(call) && hasChanged) { |
| adjustVideoMode(call); |
| } |
| } |
| |
| private boolean isCameraRequired() { |
| return primaryCall != null |
| && isCameraRequired( |
| primaryCall.getVideoState(), primaryCall.getVideoTech().getSessionModificationState()); |
| } |
| |
| /** |
| * Adjusts the current video mode by setting up the preview and display surfaces as necessary. |
| * Expected to be called whenever the video state associated with a call changes (e.g. a user |
| * turns their camera on or off) to ensure the correct surfaces are shown/hidden. TODO(vt): Need |
| * to adjust size and orientation of preview surface here. |
| */ |
| private void adjustVideoMode(DialerCall call) { |
| VideoCall videoCall = call.getVideoCall(); |
| int newVideoState = call.getVideoState(); |
| |
| LogUtil.i( |
| "VideoCallPresenter.adjustVideoMode", |
| "videoCall: %s, videoState: %d", |
| videoCall, |
| newVideoState); |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.adjustVideoMode", "error VideoCallScreen is null so returning"); |
| return; |
| } |
| |
| showVideoUi( |
| newVideoState, |
| call.getState(), |
| call.getVideoTech().getSessionModificationState(), |
| call.isRemotelyHeld()); |
| |
| // Communicate the current camera to telephony and make a request for the camera |
| // capabilities. |
| if (videoCall != null) { |
| Surface surface = getRemoteVideoSurfaceTexture().getSavedSurface(); |
| if (surface != null) { |
| LogUtil.v( |
| "VideoCallPresenter.adjustVideoMode", "calling setDisplaySurface with: " + surface); |
| videoCall.setDisplaySurface(surface); |
| } |
| |
| Assert.checkState( |
| deviceOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN); |
| videoCall.setDeviceOrientation(deviceOrientation); |
| enableCamera( |
| call, isCameraRequired(newVideoState, call.getVideoTech().getSessionModificationState())); |
| } |
| int previousVideoState = currentVideoState; |
| currentVideoState = newVideoState; |
| isVideoMode = true; |
| |
| // adjustVideoMode may be called if we are already in a 1-way video state. In this case |
| // we do not want to trigger auto-fullscreen mode. |
| if (!isVideoCall(previousVideoState) && isVideoCall(newVideoState)) { |
| maybeAutoEnterFullscreen(call); |
| } |
| } |
| |
| private static boolean shouldShowVideoUiForCall(@Nullable DialerCall call) { |
| if (call == null) { |
| return false; |
| } |
| |
| if (isVideoCall(call)) { |
| return true; |
| } |
| |
| if (isVideoUpgrade(call)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private void enableCamera(DialerCall call, boolean isCameraRequired) { |
| LogUtil.v("VideoCallPresenter.enableCamera", "call: %s, enabling: %b", call, isCameraRequired); |
| if (call == null) { |
| LogUtil.i("VideoCallPresenter.enableCamera", "call is null"); |
| return; |
| } |
| |
| boolean hasCameraPermission = VideoUtils.hasCameraPermissionAndShownPrivacyToast(context); |
| if (!hasCameraPermission) { |
| call.getVideoTech().setCamera(null); |
| previewSurfaceState = PreviewSurfaceState.NONE; |
| // TODO(wangqi): Inform remote party that the video is off. This is similar to a bug. |
| } else if (isCameraRequired) { |
| InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager(); |
| call.getVideoTech().setCamera(cameraManager.getActiveCameraId()); |
| previewSurfaceState = PreviewSurfaceState.CAMERA_SET; |
| } else { |
| previewSurfaceState = PreviewSurfaceState.NONE; |
| call.getVideoTech().setCamera(null); |
| } |
| } |
| |
| /** Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */ |
| private void exitVideoMode() { |
| LogUtil.i("VideoCallPresenter.exitVideoMode", ""); |
| |
| showVideoUi( |
| VideoProfile.STATE_AUDIO_ONLY, |
| DialerCallState.ACTIVE, |
| SessionModificationState.NO_REQUEST, |
| false /* isRemotelyHeld */); |
| enableCamera(primaryCall, false); |
| InCallPresenter.getInstance().setFullScreen(false); |
| InCallPresenter.getInstance().enableScreenTimeout(false); |
| isVideoMode = false; |
| } |
| |
| /** |
| * Based on the current video state and call state, show or hide the incoming and outgoing video |
| * surfaces. The outgoing video surface is shown any time video is transmitting. The incoming |
| * video surface is shown whenever the video is un-paused and active. |
| * |
| * @param videoState The video state. |
| * @param callState The call state. |
| */ |
| private void showVideoUi( |
| int videoState, |
| int callState, |
| @SessionModificationState int sessionModificationState, |
| boolean isRemotelyHeld) { |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.showVideoUi", "videoCallScreen is null returning"); |
| return; |
| } |
| boolean showIncomingVideo = showIncomingVideo(videoState, callState); |
| boolean showOutgoingVideo = showOutgoingVideo(context, videoState, sessionModificationState); |
| LogUtil.i( |
| "VideoCallPresenter.showVideoUi", |
| "showIncoming: %b, showOutgoing: %b, isRemotelyHeld: %b", |
| showIncomingVideo, |
| showOutgoingVideo, |
| isRemotelyHeld); |
| updateRemoteVideoSurfaceDimensions(); |
| videoCallScreen.showVideoViews(showOutgoingVideo, showIncomingVideo, isRemotelyHeld); |
| |
| InCallPresenter.getInstance().enableScreenTimeout(VideoProfile.isAudioOnly(videoState)); |
| updateFullscreenAndGreenScreenMode(callState, sessionModificationState); |
| } |
| |
| /** |
| * Handles peer video dimension changes. |
| * |
| * @param call The call which experienced a peer video dimension change. |
| * @param width The new peer video width . |
| * @param height The new peer video height. |
| */ |
| @Override |
| public void onUpdatePeerDimensions(DialerCall call, int width, int height) { |
| LogUtil.i("VideoCallPresenter.onUpdatePeerDimensions", "width: %d, height: %d", width, height); |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.onUpdatePeerDimensions", "videoCallScreen is null"); |
| return; |
| } |
| if (!call.equals(primaryCall)) { |
| LogUtil.e( |
| "VideoCallPresenter.onUpdatePeerDimensions", "current call is not equal to primary"); |
| return; |
| } |
| |
| // Change size of display surface to match the peer aspect ratio |
| if (width > 0 && height > 0 && videoCallScreen != null) { |
| getRemoteVideoSurfaceTexture().setSourceVideoDimensions(new Point(width, height)); |
| videoCallScreen.onRemoteVideoDimensionsChanged(); |
| } |
| } |
| |
| /** |
| * Handles a change to the dimensions of the local camera. Receiving the camera capabilities |
| * triggers the creation of the video |
| * |
| * @param call The call which experienced the camera dimension change. |
| * @param width The new camera video width. |
| * @param height The new camera video height. |
| */ |
| @Override |
| public void onCameraDimensionsChange(DialerCall call, int width, int height) { |
| LogUtil.i( |
| "VideoCallPresenter.onCameraDimensionsChange", |
| "call: %s, width: %d, height: %d", |
| call, |
| width, |
| height); |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "ui is null"); |
| return; |
| } |
| |
| if (!call.equals(primaryCall)) { |
| LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "not the primary call"); |
| return; |
| } |
| |
| previewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED; |
| changePreviewDimensions(width, height); |
| |
| // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}. |
| // If it not yet ready, it will be set when when creation completes. |
| Surface surface = getLocalVideoSurfaceTexture().getSavedSurface(); |
| if (surface != null) { |
| previewSurfaceState = PreviewSurfaceState.SURFACE_SET; |
| videoCall.setPreviewSurface(surface); |
| } |
| } |
| |
| /** |
| * Changes the dimensions of the preview surface. |
| * |
| * @param width The new width. |
| * @param height The new height. |
| */ |
| private void changePreviewDimensions(int width, int height) { |
| if (videoCallScreen == null) { |
| return; |
| } |
| |
| // Resize the surface used to display the preview video |
| getLocalVideoSurfaceTexture().setSurfaceDimensions(new Point(width, height)); |
| videoCallScreen.onLocalVideoDimensionsChanged(); |
| } |
| |
| /** |
| * Handles changes to the device orientation. |
| * |
| * @param orientation The screen orientation of the device (one of: {@link |
| * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link |
| * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link |
| * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link |
| * InCallOrientationEventListener#SCREEN_ORIENTATION_270}). |
| */ |
| @Override |
| public void onDeviceOrientationChanged(int orientation) { |
| LogUtil.i( |
| "VideoCallPresenter.onDeviceOrientationChanged", |
| "orientation: %d -> %d", |
| deviceOrientation, |
| orientation); |
| deviceOrientation = orientation; |
| |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.onDeviceOrientationChanged", "videoCallScreen is null"); |
| return; |
| } |
| |
| Point previewDimensions = getLocalVideoSurfaceTexture().getSurfaceDimensions(); |
| if (previewDimensions == null) { |
| return; |
| } |
| LogUtil.v( |
| "VideoCallPresenter.onDeviceOrientationChanged", |
| "orientation: %d, size: %s", |
| orientation, |
| previewDimensions); |
| changePreviewDimensions(previewDimensions.x, previewDimensions.y); |
| |
| videoCallScreen.onLocalVideoOrientationChanged(); |
| } |
| |
| /** |
| * Exits fullscreen mode if the current call context has changed to a non-video call. |
| * |
| * @param call The call. |
| */ |
| protected void maybeExitFullscreen(DialerCall call) { |
| if (call == null) { |
| return; |
| } |
| |
| if (!isVideoCall(call) || call.getState() == DialerCallState.INCOMING) { |
| LogUtil.i("VideoCallPresenter.maybeExitFullscreen", "exiting fullscreen"); |
| InCallPresenter.getInstance().setFullScreen(false); |
| } |
| } |
| |
| /** |
| * Schedules auto-entering of fullscreen mode. Will not enter full screen mode if any of the |
| * following conditions are met: 1. No call 2. DialerCall is not active 3. The current video state |
| * is not bi-directional. 4. Already in fullscreen mode 5. In accessibility mode |
| * |
| * @param call The current call. |
| */ |
| protected void maybeAutoEnterFullscreen(DialerCall call) { |
| if (!isAutoFullscreenEnabled) { |
| return; |
| } |
| |
| if (call == null |
| || call.getState() != DialerCallState.ACTIVE |
| || !isBidirectionalVideoCall(call) |
| || InCallPresenter.getInstance().isFullscreen() |
| || (context != null && AccessibilityUtil.isTouchExplorationEnabled(context))) { |
| // Ensure any previously scheduled attempt to enter fullscreen is cancelled. |
| cancelAutoFullScreen(); |
| return; |
| } |
| |
| if (autoFullScreenPending) { |
| LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "already pending."); |
| return; |
| } |
| LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "scheduled"); |
| autoFullScreenPending = true; |
| handler.removeCallbacks(autoFullscreenRunnable); |
| handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis); |
| } |
| |
| /** Cancels pending auto fullscreen mode. */ |
| @Override |
| public void cancelAutoFullScreen() { |
| if (!autoFullScreenPending) { |
| LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "none pending."); |
| return; |
| } |
| LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "cancelling pending"); |
| autoFullScreenPending = false; |
| handler.removeCallbacks(autoFullscreenRunnable); |
| } |
| |
| @Override |
| public boolean shouldShowCameraPermissionToast() { |
| if (primaryCall == null) { |
| LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "null call"); |
| return false; |
| } |
| if (primaryCall.didShowCameraPermission()) { |
| LogUtil.i( |
| "VideoCallPresenter.shouldShowCameraPermissionToast", "already shown for this call"); |
| return false; |
| } |
| return !VideoUtils.hasCameraPermission(context) |
| || !PermissionsUtil.hasCameraPrivacyToastShown(context); |
| } |
| |
| @Override |
| public void onCameraPermissionDialogShown() { |
| if (primaryCall != null) { |
| primaryCall.setDidShowCameraPermission(true); |
| } |
| } |
| |
| private void updateRemoteVideoSurfaceDimensions() { |
| Activity activity = videoCallScreen.getVideoCallScreenFragment().getActivity(); |
| if (activity != null) { |
| Point screenSize = new Point(); |
| activity.getDisplay().getSize(screenSize); |
| getRemoteVideoSurfaceTexture().setSurfaceDimensions(screenSize); |
| } |
| } |
| |
| private static boolean isVideoUpgrade(DialerCall call) { |
| return call != null |
| && (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest()); |
| } |
| |
| private static boolean isVideoUpgrade(@SessionModificationState int state) { |
| return VideoUtils.hasSentVideoUpgradeRequest(state) |
| || VideoUtils.hasReceivedVideoUpgradeRequest(state); |
| } |
| |
| @Override |
| public void onIncomingCall(DialerCall call) {} |
| |
| @Override |
| public void onUpgradeToVideo(DialerCall call) {} |
| |
| @Override |
| public void onSessionModificationStateChange(DialerCall call) {} |
| |
| @Override |
| public void onCallListChange(CallList callList) {} |
| |
| @Override |
| public void onDisconnect(DialerCall call) {} |
| |
| @Override |
| public void onWiFiToLteHandover(DialerCall call) { |
| if (call.isVideoCall() || call.hasSentVideoUpgradeRequest()) { |
| videoCallScreen.onHandoverFromWiFiToLte(); |
| } |
| } |
| |
| @Override |
| public void onHandoverToWifiFailed(DialerCall call) {} |
| |
| @Override |
| public void onInternationalCallOnWifi(@NonNull DialerCall call) {} |
| |
| private class LocalDelegate implements VideoSurfaceDelegate { |
| @Override |
| public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) { |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no UI"); |
| return; |
| } |
| if (videoCall == null) { |
| LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no video call"); |
| return; |
| } |
| |
| // If the preview surface has just been created and we have already received camera |
| // capabilities, but not yet set the surface, we will set the surface now. |
| if (previewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { |
| previewSurfaceState = PreviewSurfaceState.SURFACE_SET; |
| videoCall.setPreviewSurface(videoCallSurface.getSavedSurface()); |
| } else if (previewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) { |
| enableCamera(primaryCall, true); |
| } |
| } |
| |
| @Override |
| public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) { |
| if (videoCall == null) { |
| LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceReleased", "no video call"); |
| return; |
| } |
| |
| videoCall.setPreviewSurface(null); |
| enableCamera(primaryCall, false); |
| } |
| |
| @Override |
| public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) { |
| if (videoCall == null) { |
| LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", "no video call"); |
| return; |
| } |
| |
| boolean isChangingConfigurations = InCallPresenter.getInstance().isChangingConfigurations(); |
| if (!isChangingConfigurations) { |
| enableCamera(primaryCall, false); |
| } else { |
| LogUtil.i( |
| "VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", |
| "activity is being destroyed due to configuration changes. Not closing the camera."); |
| } |
| } |
| |
| @Override |
| public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) { |
| VideoCallPresenter.this.onSurfaceClick(); |
| } |
| } |
| |
| private class RemoteDelegate implements VideoSurfaceDelegate { |
| @Override |
| public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) { |
| if (videoCallScreen == null) { |
| LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no UI"); |
| return; |
| } |
| if (videoCall == null) { |
| LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no video call"); |
| return; |
| } |
| videoCall.setDisplaySurface(videoCallSurface.getSavedSurface()); |
| } |
| |
| @Override |
| public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) { |
| if (videoCall == null) { |
| LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceReleased", "no video call"); |
| return; |
| } |
| videoCall.setDisplaySurface(null); |
| } |
| |
| @Override |
| public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {} |
| |
| @Override |
| public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) { |
| VideoCallPresenter.this.onSurfaceClick(); |
| } |
| } |
| |
| /** Defines the state of the preview surface negotiation with the telephony layer. */ |
| private static class PreviewSurfaceState { |
| |
| /** |
| * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet started. |
| */ |
| private static final int NONE = 0; |
| |
| /** |
| * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet been |
| * received. |
| */ |
| private static final int CAMERA_SET = 1; |
| |
| /** |
| * The camera capabilties have been received from telephony, but the surface has not yet been |
| * set on the {@link VideoCall}. |
| */ |
| private static final int CAPABILITIES_RECEIVED = 2; |
| |
| /** The surface has been set on the {@link VideoCall}. */ |
| private static final int SURFACE_SET = 3; |
| } |
| |
| private static boolean isBidirectionalVideoCall(DialerCall call) { |
| return VideoProfile.isBidirectional(call.getVideoState()); |
| } |
| |
| private static boolean isIncomingVideoCall(DialerCall call) { |
| if (!isVideoCall(call)) { |
| return false; |
| } |
| final int state = call.getState(); |
| return (state == DialerCallState.INCOMING) || (state == DialerCallState.CALL_WAITING); |
| } |
| |
| private static boolean isActiveVideoCall(DialerCall call) { |
| return isVideoCall(call) && call.getState() == DialerCallState.ACTIVE; |
| } |
| |
| private static boolean isOutgoingVideoCall(DialerCall call) { |
| if (!isVideoCall(call)) { |
| return false; |
| } |
| final int state = call.getState(); |
| return DialerCallState.isDialing(state) |
| || state == DialerCallState.CONNECTING |
| || state == DialerCallState.SELECT_PHONE_ACCOUNT; |
| } |
| |
| private static boolean isAudioCall(DialerCall call) { |
| return call != null && VideoProfile.isAudioOnly(call.getVideoState()); |
| } |
| |
| private static boolean isVideoCall(@Nullable DialerCall call) { |
| return call != null && call.isVideoCall(); |
| } |
| |
| private static boolean isVideoCall(int videoState) { |
| return VideoProfile.isTransmissionEnabled(videoState) |
| || VideoProfile.isReceptionEnabled(videoState); |
| } |
| } |