blob: 0d1d1a5e0d21cbbb51ad137bc3e75ee39185821c [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.incallui;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.Point;
22import android.os.Handler;
wangqida410d32018-03-06 16:51:38 -080023import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080024import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.telecom.InCallService.VideoCall;
26import android.telecom.VideoProfile;
27import android.telecom.VideoProfile.CameraCapabilities;
28import android.view.Surface;
Eric Erfanian90508232017-03-24 09:31:16 -070029import android.view.SurfaceView;
Eric Erfanianccca3152017-02-22 16:32:36 -080030import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080031import com.android.dialer.common.LogUtil;
zachh190343a2018-05-31 17:30:46 -070032import com.android.dialer.configprovider.ConfigProviderComponent;
Eric Erfanian2ca43182017-08-31 06:57:16 -070033import com.android.dialer.util.PermissionsUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080034import com.android.incallui.InCallPresenter.InCallDetailsListener;
35import com.android.incallui.InCallPresenter.InCallOrientationListener;
36import com.android.incallui.InCallPresenter.InCallStateListener;
37import com.android.incallui.InCallPresenter.IncomingCallListener;
38import com.android.incallui.call.CallList;
39import com.android.incallui.call.DialerCall;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070040import com.android.incallui.call.DialerCall.CameraDirection;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import com.android.incallui.call.InCallVideoCallCallbackNotifier;
42import com.android.incallui.call.InCallVideoCallCallbackNotifier.SurfaceChangeListener;
wangqibb94ca62018-04-27 14:34:04 -070043import com.android.incallui.call.state.DialerCallState;
Eric Erfanianccca3152017-02-22 16:32:36 -080044import com.android.incallui.util.AccessibilityUtil;
45import com.android.incallui.video.protocol.VideoCallScreen;
46import com.android.incallui.video.protocol.VideoCallScreenDelegate;
47import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate;
48import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
Eric Erfanian90508232017-03-24 09:31:16 -070049import com.android.incallui.videotech.utils.SessionModificationState;
50import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import java.util.Objects;
52
53/**
54 * Logic related to the {@link VideoCallScreen} and for managing changes to the video calling
55 * surfaces based on other user interface events and incoming events from the {@class
56 * VideoCallListener}.
57 *
58 * <p>When a call's video state changes to bi-directional video, the {@link
59 * com.android.incallui.VideoCallPresenter} performs the following negotiation with the telephony
60 * layer:
61 *
62 * <ul>
wangqi385a5a12017-09-28 10:44:54 -070063 * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.
64 * <li>{@code VideoCallPresenter} creates the preview surface.
65 * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.
66 * <li>Telephony layer sends {@link CameraCapabilities}, including the dimensions of the video for
67 * the current camera.
68 * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect ratio of
69 * the camera.
70 * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.
Eric Erfanianccca3152017-02-22 16:32:36 -080071 * </ul>
72 *
73 * <p>When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both
74 * surfaces.
75 */
76public class VideoCallPresenter
77 implements IncomingCallListener,
78 InCallOrientationListener,
79 InCallStateListener,
80 InCallDetailsListener,
81 SurfaceChangeListener,
Eric Erfanianccca3152017-02-22 16:32:36 -080082 InCallPresenter.InCallEventListener,
wangqida410d32018-03-06 16:51:38 -080083 VideoCallScreenDelegate,
84 CallList.Listener {
Eric Erfanianccca3152017-02-22 16:32:36 -080085
linyuh183cb712017-12-27 17:02:37 -080086 private static boolean isVideoMode = false;
Eric Erfanianccca3152017-02-22 16:32:36 -080087
linyuh183cb712017-12-27 17:02:37 -080088 private final Handler handler = new Handler();
89 private VideoCallScreen videoCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -080090
91 /** The current context. */
linyuh183cb712017-12-27 17:02:37 -080092 private Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -080093
Eric Erfanianccca3152017-02-22 16:32:36 -080094 /** The call the video surfaces are currently related to */
linyuh183cb712017-12-27 17:02:37 -080095 private DialerCall primaryCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080096 /**
97 * The {@link VideoCall} used to inform the video telephony layer of changes to the video
98 * surfaces.
99 */
linyuh183cb712017-12-27 17:02:37 -0800100 private VideoCall videoCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800101 /** Determines if the current UI state represents a video call. */
linyuh183cb712017-12-27 17:02:37 -0800102 private int currentVideoState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800103 /** DialerCall's current state */
wangqibb94ca62018-04-27 14:34:04 -0700104 private int currentCallState = DialerCallState.INVALID;
Eric Erfanianccca3152017-02-22 16:32:36 -0800105 /** Determines the device orientation (portrait/lanscape). */
linyuh183cb712017-12-27 17:02:37 -0800106 private int deviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800107 /** Tracks the state of the preview surface negotiation with the telephony layer. */
linyuh183cb712017-12-27 17:02:37 -0800108 private int previewSurfaceState = PreviewSurfaceState.NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800109 /**
110 * Determines whether video calls should automatically enter full screen mode after {@link
linyuh183cb712017-12-27 17:02:37 -0800111 * #autoFullscreenTimeoutMillis} milliseconds.
Eric Erfanianccca3152017-02-22 16:32:36 -0800112 */
linyuh183cb712017-12-27 17:02:37 -0800113 private boolean isAutoFullscreenEnabled = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800114 /**
115 * Determines the number of milliseconds after which a video call will automatically enter
linyuh183cb712017-12-27 17:02:37 -0800116 * fullscreen mode. Requires {@link #isAutoFullscreenEnabled} to be {@code true}.
Eric Erfanianccca3152017-02-22 16:32:36 -0800117 */
linyuh183cb712017-12-27 17:02:37 -0800118 private int autoFullscreenTimeoutMillis = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800119 /**
120 * Determines if the countdown is currently running to automatically enter full screen video mode.
121 */
linyuh183cb712017-12-27 17:02:37 -0800122 private boolean autoFullScreenPending = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800123 /** Whether if the call is remotely held. */
linyuh183cb712017-12-27 17:02:37 -0800124 private boolean isRemotelyHeld = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800125 /**
126 * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto
127 * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit the
128 * dialpad).
129 */
linyuh183cb712017-12-27 17:02:37 -0800130 private Runnable autoFullscreenRunnable =
Eric Erfanianccca3152017-02-22 16:32:36 -0800131 new Runnable() {
132 @Override
133 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800134 if (autoFullScreenPending
Eric Erfanianccca3152017-02-22 16:32:36 -0800135 && !InCallPresenter.getInstance().isDialpadVisible()
linyuh183cb712017-12-27 17:02:37 -0800136 && isVideoMode) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800137
138 LogUtil.v("VideoCallPresenter.mAutoFullScreenRunnable", "entering fullscreen mode");
139 InCallPresenter.getInstance().setFullScreen(true);
linyuh183cb712017-12-27 17:02:37 -0800140 autoFullScreenPending = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800141 } else {
142 LogUtil.v(
143 "VideoCallPresenter.mAutoFullScreenRunnable",
144 "skipping scheduled fullscreen mode.");
145 }
146 }
147 };
148
149 private boolean isVideoCallScreenUiReady;
150
151 private static boolean isCameraRequired(int videoState, int sessionModificationState) {
152 return VideoProfile.isBidirectional(videoState)
153 || VideoProfile.isTransmissionEnabled(videoState)
154 || isVideoUpgrade(sessionModificationState);
155 }
156
157 /**
158 * Determines if the incoming video surface should be shown based on the current videoState and
Eric Erfaniand8046e52017-04-06 09:41:50 -0700159 * callState. The video surface is shown when incoming video is not paused, the call is active or
160 * dialing and video reception is enabled.
Eric Erfanianccca3152017-02-22 16:32:36 -0800161 *
162 * @param videoState The current video state.
163 * @param callState The current call state.
164 * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise.
165 */
linyuh122fb0b2018-03-26 13:35:32 -0700166 static boolean showIncomingVideo(int videoState, int callState) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800167
168 boolean isPaused = VideoProfile.isPaused(videoState);
wangqibb94ca62018-04-27 14:34:04 -0700169 boolean isCallActive = callState == DialerCallState.ACTIVE;
wangqi385a5a12017-09-28 10:44:54 -0700170 // Show incoming Video for dialing calls to support early media
Eric Erfaniand8046e52017-04-06 09:41:50 -0700171 boolean isCallOutgoingPending =
wangqibb94ca62018-04-27 14:34:04 -0700172 DialerCallState.isDialing(callState) || callState == DialerCallState.CONNECTING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800173
Eric Erfaniand8046e52017-04-06 09:41:50 -0700174 return !isPaused
175 && (isCallActive || isCallOutgoingPending)
176 && VideoProfile.isReceptionEnabled(videoState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800177 }
178
179 /**
180 * Determines if the outgoing video surface should be shown based on the current videoState. The
181 * video surface is shown if video transmission is enabled.
182 *
183 * @return {@code true} if the the outgoing video surface should be shown, {@code false}
184 * otherwise.
185 */
linyuh122fb0b2018-03-26 13:35:32 -0700186 private static boolean showOutgoingVideo(
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 Context context, int videoState, int sessionModificationState) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700188 if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(context)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 LogUtil.i("VideoCallPresenter.showOutgoingVideo", "Camera permission is disabled by user.");
190 return false;
191 }
192
Eric Erfanianccca3152017-02-22 16:32:36 -0800193 return VideoProfile.isTransmissionEnabled(videoState)
194 || isVideoUpgrade(sessionModificationState);
195 }
196
197 private static void updateCameraSelection(DialerCall call) {
198 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + call);
199 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + toSimpleString(call));
200
201 final DialerCall activeCall = CallList.getInstance().getActiveCall();
202 int cameraDir;
203
204 // this function should never be called with null call object, however if it happens we
205 // should handle it gracefully.
206 if (call == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700207 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800208 LogUtil.e(
209 "VideoCallPresenter.updateCameraSelection",
210 "call is null. Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
211 }
212
213 // Clear camera direction if this is not a video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700214 else if (isAudioCall(call) && !isVideoUpgrade(call)) {
215 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
216 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800217 }
218
219 // If this is a waiting video call, default to active call's camera,
220 // since we don't want to change the current camera for waiting call
221 // without user's permission.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700222 else if (isVideoCall(activeCall) && isIncomingVideoCall(call)) {
223 cameraDir = activeCall.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800224 }
225
226 // Infer the camera direction from the video state and store it,
227 // if this is an outgoing video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700228 else if (isOutgoingVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700230 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800231 }
232
233 // Use the stored camera dir if this is an outgoing video call for which camera direction
234 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700235 else if (isOutgoingVideoCall(call)) {
236 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800237 }
238
239 // Infer the camera direction from the video state and store it,
240 // if this is an active video call and camera direction is not set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700241 else if (isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800242 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700243 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800244 }
245
246 // Use the stored camera dir if this is an active video call for which camera direction
247 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700248 else if (isActiveVideoCall(call)) {
249 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800250 }
251
252 // For all other cases infer the camera direction but don't store it in the call object.
253 else {
254 cameraDir = toCameraDirection(call.getVideoState());
255 }
256
257 LogUtil.i(
258 "VideoCallPresenter.updateCameraSelection",
259 "setting camera direction to %d, call: %s",
260 cameraDir,
261 call);
262 final InCallCameraManager cameraManager =
263 InCallPresenter.getInstance().getInCallCameraManager();
264 cameraManager.setUseFrontFacingCamera(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700265 cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800266 }
267
268 private static int toCameraDirection(int videoState) {
269 return VideoProfile.isTransmissionEnabled(videoState)
270 && !VideoProfile.isBidirectional(videoState)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700271 ? CameraDirection.CAMERA_DIRECTION_BACK_FACING
272 : CameraDirection.CAMERA_DIRECTION_FRONT_FACING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800273 }
274
275 private static boolean isCameraDirectionSet(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700276 return isVideoCall(call) && call.getCameraDir() != CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800277 }
278
279 private static String toSimpleString(DialerCall call) {
280 return call == null ? null : call.toSimpleString();
281 }
282
283 /**
284 * Initializes the presenter.
285 *
286 * @param context The current context.
287 */
288 @Override
289 public void initVideoCallScreenDelegate(Context context, VideoCallScreen videoCallScreen) {
linyuh183cb712017-12-27 17:02:37 -0800290 this.context = context;
291 this.videoCallScreen = videoCallScreen;
292 isAutoFullscreenEnabled =
293 this.context.getResources().getBoolean(R.bool.video_call_auto_fullscreen);
294 autoFullscreenTimeoutMillis =
295 this.context.getResources().getInteger(R.integer.video_call_auto_fullscreen_timeout);
Eric Erfanianccca3152017-02-22 16:32:36 -0800296 }
297
298 /** Called when the user interface is ready to be used. */
299 @Override
300 public void onVideoCallScreenUiReady() {
301 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiReady", "");
302 Assert.checkState(!isVideoCallScreenUiReady);
303
linyuh183cb712017-12-27 17:02:37 -0800304 deviceOrientation = InCallOrientationEventListener.getCurrentOrientation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800305
306 // Register for call state changes last
307 InCallPresenter.getInstance().addListener(this);
308 InCallPresenter.getInstance().addDetailsListener(this);
309 InCallPresenter.getInstance().addIncomingCallListener(this);
310 InCallPresenter.getInstance().addOrientationListener(this);
311 // To get updates of video call details changes
312 InCallPresenter.getInstance().addInCallEventListener(this);
313 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate());
314 InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate());
315
wangqida410d32018-03-06 16:51:38 -0800316 CallList.getInstance().addListener(this);
317
Eric Erfanianccca3152017-02-22 16:32:36 -0800318 // Register for surface and video events from {@link InCallVideoCallListener}s.
319 InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
linyuh183cb712017-12-27 17:02:37 -0800320 currentVideoState = VideoProfile.STATE_AUDIO_ONLY;
wangqibb94ca62018-04-27 14:34:04 -0700321 currentCallState = DialerCallState.INVALID;
Eric Erfanianccca3152017-02-22 16:32:36 -0800322
323 InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState();
324 onStateChange(inCallState, inCallState, CallList.getInstance());
325 isVideoCallScreenUiReady = true;
Sekine Yasuakied336cb2017-07-04 16:50:09 +0900326
327 Point sourceVideoDimensions = getRemoteVideoSurfaceTexture().getSourceVideoDimensions();
328 if (sourceVideoDimensions != null && primaryCall != null) {
329 int width = primaryCall.getPeerDimensionWidth();
330 int height = primaryCall.getPeerDimensionHeight();
331 boolean updated = DialerCall.UNKNOWN_PEER_DIMENSIONS != width
332 && DialerCall.UNKNOWN_PEER_DIMENSIONS != height;
333 if (updated && (sourceVideoDimensions.x != width || sourceVideoDimensions.y != height)) {
334 onUpdatePeerDimensions(primaryCall, width, height);
335 }
336 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800337 }
338
339 /** Called when the user interface is no longer ready to be used. */
340 @Override
341 public void onVideoCallScreenUiUnready() {
342 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiUnready", "");
343 Assert.checkState(isVideoCallScreenUiReady);
344
Eric Erfanianccca3152017-02-22 16:32:36 -0800345 cancelAutoFullScreen();
346
347 InCallPresenter.getInstance().removeListener(this);
348 InCallPresenter.getInstance().removeDetailsListener(this);
349 InCallPresenter.getInstance().removeIncomingCallListener(this);
350 InCallPresenter.getInstance().removeOrientationListener(this);
351 InCallPresenter.getInstance().removeInCallEventListener(this);
352 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null);
353
wangqida410d32018-03-06 16:51:38 -0800354 CallList.getInstance().removeListener(this);
355
Eric Erfanianccca3152017-02-22 16:32:36 -0800356 InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800357
358 // Ensure that the call's camera direction is updated (most likely to UNKNOWN). Normally this
359 // happens after any call state changes but we're unregistering from InCallPresenter above so
Eric Erfanian938468d2017-10-24 14:05:52 -0700360 // we won't get any more call state changes. See a bug.
linyuh183cb712017-12-27 17:02:37 -0800361 if (primaryCall != null) {
362 updateCameraSelection(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800363 }
364
365 isVideoCallScreenUiReady = false;
366 }
367
368 /**
369 * Handles clicks on the video surfaces. If not currently in fullscreen mode, will set fullscreen.
370 */
371 private void onSurfaceClick() {
372 LogUtil.i("VideoCallPresenter.onSurfaceClick", "");
373 cancelAutoFullScreen();
374 if (!InCallPresenter.getInstance().isFullscreen()) {
375 InCallPresenter.getInstance().setFullScreen(true);
376 } else {
377 InCallPresenter.getInstance().setFullScreen(false);
linyuh183cb712017-12-27 17:02:37 -0800378 maybeAutoEnterFullscreen(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800379 // If Activity is not multiwindow, fullscreen will be driven by SystemUI visibility changes
380 // instead. See #onSystemUiVisibilityChange(boolean)
381
382 // TODO (keyboardr): onSystemUiVisibilityChange isn't being called the first time
383 // visibility changes after orientation change, so this is currently always done as a backup.
384 }
385 }
386
387 @Override
388 public void onSystemUiVisibilityChange(boolean visible) {
389 // If the SystemUI has changed to be visible, take us out of fullscreen mode
390 LogUtil.i("VideoCallPresenter.onSystemUiVisibilityChange", "visible: " + visible);
391 if (visible) {
392 InCallPresenter.getInstance().setFullScreen(false);
linyuh183cb712017-12-27 17:02:37 -0800393 maybeAutoEnterFullscreen(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800394 }
395 }
396
397 @Override
398 public VideoSurfaceTexture getLocalVideoSurfaceTexture() {
399 return InCallPresenter.getInstance().getLocalVideoSurfaceTexture();
400 }
401
402 @Override
403 public VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
404 return InCallPresenter.getInstance().getRemoteVideoSurfaceTexture();
405 }
406
407 @Override
Eric Erfanian90508232017-03-24 09:31:16 -0700408 public void setSurfaceViews(SurfaceView preview, SurfaceView remote) {
409 throw Assert.createUnsupportedOperationFailException();
410 }
411
412 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -0800413 public int getDeviceOrientation() {
linyuh183cb712017-12-27 17:02:37 -0800414 return deviceOrientation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800415 }
416
417 /**
418 * This should only be called when user approved the camera permission, which is local action and
419 * does NOT change any call states.
420 */
421 @Override
422 public void onCameraPermissionGranted() {
423 LogUtil.i("VideoCallPresenter.onCameraPermissionGranted", "");
linyuh183cb712017-12-27 17:02:37 -0800424 PermissionsUtil.setCameraPrivacyToastShown(context);
425 enableCamera(primaryCall, isCameraRequired());
Eric Erfanianccca3152017-02-22 16:32:36 -0800426 showVideoUi(
linyuh183cb712017-12-27 17:02:37 -0800427 primaryCall.getVideoState(),
428 primaryCall.getState(),
429 primaryCall.getVideoTech().getSessionModificationState(),
430 primaryCall.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800431 InCallPresenter.getInstance().getInCallCameraManager().onCameraPermissionGranted();
432 }
433
Hideki Ishiie39b9262017-07-25 21:29:43 +0900434 @Override
435 public boolean isFullscreen() {
436 return InCallPresenter.getInstance().isFullscreen();
437 }
438
Eric Erfanianccca3152017-02-22 16:32:36 -0800439 /**
440 * Called when the user interacts with the UI. If a fullscreen timer is pending then we start the
441 * timer from scratch to avoid having the UI disappear while the user is interacting with it.
442 */
443 @Override
444 public void resetAutoFullscreenTimer() {
linyuh183cb712017-12-27 17:02:37 -0800445 if (autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 LogUtil.i("VideoCallPresenter.resetAutoFullscreenTimer", "resetting");
linyuh183cb712017-12-27 17:02:37 -0800447 handler.removeCallbacks(autoFullscreenRunnable);
448 handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis);
Eric Erfanianccca3152017-02-22 16:32:36 -0800449 }
450 }
451
452 /**
453 * Handles incoming calls.
454 *
455 * @param oldState The old in call state.
456 * @param newState The new in call state.
457 * @param call The call.
458 */
459 @Override
460 public void onIncomingCall(
461 InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, DialerCall call) {
wangqi385a5a12017-09-28 10:44:54 -0700462 // If video call screen ui is already destroyed, this shouldn't be called. But the UI may be
463 // updated synchronized by {@link CallCardPresenter#onIncomingCall} before this is called, this
464 // could still be called. Thus just do nothing in this case.
465 if (!isVideoCallScreenUiReady) {
466 LogUtil.i("VideoCallPresenter.onIncomingCall", "UI is not ready");
467 return;
468 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800469 // same logic should happen as with onStateChange()
470 onStateChange(oldState, newState, CallList.getInstance());
471 }
472
473 /**
474 * Handles state changes (including incoming calls)
475 *
476 * @param newState The in call state.
477 * @param callList The call list.
478 */
479 @Override
480 public void onStateChange(
481 InCallPresenter.InCallState oldState,
482 InCallPresenter.InCallState newState,
483 CallList callList) {
484 LogUtil.v(
485 "VideoCallPresenter.onStateChange",
486 "oldState: %s, newState: %s, isVideoMode: %b",
487 oldState,
488 newState,
489 isVideoMode());
490
491 if (newState == InCallPresenter.InCallState.NO_CALLS) {
492 if (isVideoMode()) {
493 exitVideoMode();
494 }
495
496 InCallPresenter.getInstance().cleanupSurfaces();
497 }
498
499 // Determine the primary active call).
500 DialerCall primary = null;
501
502 // Determine the call which is the focus of the user's attention. In the case of an
503 // incoming call waiting call, the primary call is still the active video call, however
504 // the determination of whether we should be in fullscreen mode is based on the type of the
505 // incoming call, not the active video call.
506 DialerCall currentCall = null;
507
508 if (newState == InCallPresenter.InCallState.INCOMING) {
509 // We don't want to replace active video call (primary call)
510 // with a waiting call, since user may choose to ignore/decline the waiting call and
511 // this should have no impact on current active video call, that is, we should not
512 // change the camera or UI unless the waiting VT call becomes active.
513 primary = callList.getActiveCall();
514 currentCall = callList.getIncomingCall();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700515 if (!isActiveVideoCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800516 primary = callList.getIncomingCall();
517 }
518 } else if (newState == InCallPresenter.InCallState.OUTGOING) {
519 currentCall = primary = callList.getOutgoingCall();
520 } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
521 currentCall = primary = callList.getPendingOutgoingCall();
522 } else if (newState == InCallPresenter.InCallState.INCALL) {
523 currentCall = primary = callList.getActiveCall();
524 }
525
linyuh183cb712017-12-27 17:02:37 -0800526 final boolean primaryChanged = !Objects.equals(primaryCall, primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800527 LogUtil.i(
528 "VideoCallPresenter.onStateChange",
529 "primaryChanged: %b, primary: %s, mPrimaryCall: %s",
530 primaryChanged,
531 primary,
linyuh183cb712017-12-27 17:02:37 -0800532 primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800533 if (primaryChanged) {
534 onPrimaryCallChanged(primary);
linyuh183cb712017-12-27 17:02:37 -0800535 } else if (primaryCall != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800536 updateVideoCall(primary);
537 }
538 updateCallCache(primary);
539
540 // If the call context changed, potentially exit fullscreen or schedule auto enter of
541 // fullscreen mode.
542 // If the current call context is no longer a video call, exit fullscreen mode.
543 maybeExitFullscreen(currentCall);
544 // Schedule auto-enter of fullscreen mode if the current call context is a video call
545 maybeAutoEnterFullscreen(currentCall);
546 }
547
548 /**
549 * Handles a change to the fullscreen mode of the app.
550 *
551 * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise.
552 */
553 @Override
554 public void onFullscreenModeChanged(boolean isFullscreenMode) {
555 cancelAutoFullScreen();
linyuh183cb712017-12-27 17:02:37 -0800556 if (primaryCall != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 updateFullscreenAndGreenScreenMode(
linyuh183cb712017-12-27 17:02:37 -0800558 primaryCall.getState(), primaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800559 } else {
wangqibb94ca62018-04-27 14:34:04 -0700560 updateFullscreenAndGreenScreenMode(
561 DialerCallState.INVALID, SessionModificationState.NO_REQUEST);
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 }
563 }
564
565 private void checkForVideoStateChange(DialerCall call) {
566 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
linyuh183cb712017-12-27 17:02:37 -0800567 final boolean hasVideoStateChanged = currentVideoState != call.getVideoState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800568
569 LogUtil.v(
570 "VideoCallPresenter.checkForVideoStateChange",
571 "shouldShowVideoUi: %b, hasVideoStateChanged: %b, isVideoMode: %b, previousVideoState: %s,"
572 + " newVideoState: %s",
573 shouldShowVideoUi,
574 hasVideoStateChanged,
575 isVideoMode(),
linyuh183cb712017-12-27 17:02:37 -0800576 VideoProfile.videoStateToString(currentVideoState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800577 VideoProfile.videoStateToString(call.getVideoState()));
578 if (!hasVideoStateChanged) {
579 return;
580 }
581
582 updateCameraSelection(call);
583
584 if (shouldShowVideoUi) {
585 adjustVideoMode(call);
586 } else if (isVideoMode()) {
587 exitVideoMode();
588 }
589 }
590
591 private void checkForCallStateChange(DialerCall call) {
592 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
593 final boolean hasCallStateChanged =
linyuh183cb712017-12-27 17:02:37 -0800594 currentCallState != call.getState() || isRemotelyHeld != call.isRemotelyHeld();
595 isRemotelyHeld = call.isRemotelyHeld();
Eric Erfanianccca3152017-02-22 16:32:36 -0800596
597 LogUtil.v(
598 "VideoCallPresenter.checkForCallStateChange",
599 "shouldShowVideoUi: %b, hasCallStateChanged: %b, isVideoMode: %b",
600 shouldShowVideoUi,
601 hasCallStateChanged,
602 isVideoMode());
603
604 if (!hasCallStateChanged) {
605 return;
606 }
607
608 if (shouldShowVideoUi) {
609 final InCallCameraManager cameraManager =
610 InCallPresenter.getInstance().getInCallCameraManager();
611
612 String prevCameraId = cameraManager.getActiveCameraId();
613 updateCameraSelection(call);
614 String newCameraId = cameraManager.getActiveCameraId();
615
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700616 if (!Objects.equals(prevCameraId, newCameraId) && isActiveVideoCall(call)) {
roldenburgca475472017-10-25 13:00:42 -0700617 enableCamera(call, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800618 }
619 }
620
621 // Make sure we hide or show the video UI if needed.
622 showVideoUi(
623 call.getVideoState(),
624 call.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700625 call.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800626 call.isRemotelyHeld());
627 }
628
629 private void onPrimaryCallChanged(DialerCall newPrimaryCall) {
630 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(newPrimaryCall);
631 final boolean isVideoMode = isVideoMode();
632
633 LogUtil.v(
634 "VideoCallPresenter.onPrimaryCallChanged",
635 "shouldShowVideoUi: %b, isVideoMode: %b",
636 shouldShowVideoUi,
637 isVideoMode);
638
639 if (!shouldShowVideoUi && isVideoMode) {
640 // Terminate video mode if new primary call is not a video call
641 // and we are currently in video mode.
642 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "exiting video mode...");
643 exitVideoMode();
644 } else if (shouldShowVideoUi) {
645 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "entering video mode...");
646
647 updateCameraSelection(newPrimaryCall);
648 adjustVideoMode(newPrimaryCall);
649 }
650 checkForOrientationAllowedChange(newPrimaryCall);
651 }
652
653 private boolean isVideoMode() {
linyuh183cb712017-12-27 17:02:37 -0800654 return isVideoMode;
Eric Erfanianccca3152017-02-22 16:32:36 -0800655 }
656
657 private void updateCallCache(DialerCall call) {
658 if (call == null) {
linyuh183cb712017-12-27 17:02:37 -0800659 currentVideoState = VideoProfile.STATE_AUDIO_ONLY;
wangqibb94ca62018-04-27 14:34:04 -0700660 currentCallState = DialerCallState.INVALID;
linyuh183cb712017-12-27 17:02:37 -0800661 videoCall = null;
662 primaryCall = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800663 } else {
linyuh183cb712017-12-27 17:02:37 -0800664 currentVideoState = call.getVideoState();
665 videoCall = call.getVideoCall();
666 currentCallState = call.getState();
667 primaryCall = call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800668 }
669 }
670
671 /**
672 * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in
673 * changes to the video state.
674 *
675 * @param call The call for which the details changed.
676 * @param details The new call details.
677 */
678 @Override
679 public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) {
680 LogUtil.v(
681 "VideoCallPresenter.onDetailsChanged",
682 "call: %s, details: %s, mPrimaryCall: %s",
683 call,
684 details,
linyuh183cb712017-12-27 17:02:37 -0800685 primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800686 if (call == null) {
687 return;
688 }
689 // If the details change is not for the currently active call no update is required.
linyuh183cb712017-12-27 17:02:37 -0800690 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800691 LogUtil.v("VideoCallPresenter.onDetailsChanged", "details not for current active call");
692 return;
693 }
694
695 updateVideoCall(call);
696
697 updateCallCache(call);
698 }
699
700 private void updateVideoCall(DialerCall call) {
701 checkForVideoCallChange(call);
702 checkForVideoStateChange(call);
703 checkForCallStateChange(call);
704 checkForOrientationAllowedChange(call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700705 updateFullscreenAndGreenScreenMode(
706 call.getState(), call.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800707 }
708
709 private void checkForOrientationAllowedChange(@Nullable DialerCall call) {
wangqi4a22adb2018-06-18 13:18:06 -0700710 // Call could be null when video call ended. This check could prevent unwanted orientation
711 // change before incall UI gets destroyed.
712 if (call != null) {
713 InCallPresenter.getInstance()
714 .setInCallAllowsOrientationChange(isVideoCall(call) || isVideoUpgrade(call));
715 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800716 }
717
718 private void updateFullscreenAndGreenScreenMode(
719 int callState, @SessionModificationState int sessionModificationState) {
linyuh183cb712017-12-27 17:02:37 -0800720 if (videoCallScreen != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800721 boolean shouldShowFullscreen = InCallPresenter.getInstance().isFullscreen();
722 boolean shouldShowGreenScreen =
wangqibb94ca62018-04-27 14:34:04 -0700723 callState == DialerCallState.DIALING
724 || callState == DialerCallState.CONNECTING
725 || callState == DialerCallState.INCOMING
Eric Erfanianccca3152017-02-22 16:32:36 -0800726 || isVideoUpgrade(sessionModificationState);
linyuh183cb712017-12-27 17:02:37 -0800727 videoCallScreen.updateFullscreenAndGreenScreenMode(
Eric Erfanianccca3152017-02-22 16:32:36 -0800728 shouldShowFullscreen, shouldShowGreenScreen);
729 }
730 }
731
732 /** Checks for a change to the video call and changes it if required. */
733 private void checkForVideoCallChange(DialerCall call) {
734 final VideoCall videoCall = call.getVideoCall();
735 LogUtil.v(
736 "VideoCallPresenter.checkForVideoCallChange",
737 "videoCall: %s, mVideoCall: %s",
738 videoCall,
linyuh183cb712017-12-27 17:02:37 -0800739 this.videoCall);
740 if (!Objects.equals(videoCall, this.videoCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800741 changeVideoCall(call);
742 }
743 }
744
745 /**
746 * Handles a change to the video call. Sets the surfaces on the previous call to null and sets the
747 * surfaces on the new video call accordingly.
748 *
749 * @param call The new video call.
750 */
751 private void changeVideoCall(DialerCall call) {
752 final VideoCall videoCall = call == null ? null : call.getVideoCall();
753 LogUtil.i(
754 "VideoCallPresenter.changeVideoCall",
755 "videoCall: %s, mVideoCall: %s",
756 videoCall,
linyuh183cb712017-12-27 17:02:37 -0800757 this.videoCall);
758 final boolean hasChanged = this.videoCall == null && videoCall != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800759
linyuh183cb712017-12-27 17:02:37 -0800760 this.videoCall = videoCall;
761 if (this.videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 LogUtil.v("VideoCallPresenter.changeVideoCall", "video call or primary call is null. Return");
763 return;
764 }
765
766 if (shouldShowVideoUiForCall(call) && hasChanged) {
767 adjustVideoMode(call);
768 }
769 }
770
771 private boolean isCameraRequired() {
linyuh183cb712017-12-27 17:02:37 -0800772 return primaryCall != null
Eric Erfanianccca3152017-02-22 16:32:36 -0800773 && isCameraRequired(
linyuh183cb712017-12-27 17:02:37 -0800774 primaryCall.getVideoState(), primaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800775 }
776
777 /**
778 * Adjusts the current video mode by setting up the preview and display surfaces as necessary.
779 * Expected to be called whenever the video state associated with a call changes (e.g. a user
Eric Erfanian2ca43182017-08-31 06:57:16 -0700780 * turns their camera on or off) to ensure the correct surfaces are shown/hidden. TODO(vt): Need
Eric Erfanianccca3152017-02-22 16:32:36 -0800781 * to adjust size and orientation of preview surface here.
782 */
783 private void adjustVideoMode(DialerCall call) {
784 VideoCall videoCall = call.getVideoCall();
785 int newVideoState = call.getVideoState();
786
787 LogUtil.i(
788 "VideoCallPresenter.adjustVideoMode",
789 "videoCall: %s, videoState: %d",
790 videoCall,
791 newVideoState);
linyuh183cb712017-12-27 17:02:37 -0800792 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800793 LogUtil.e("VideoCallPresenter.adjustVideoMode", "error VideoCallScreen is null so returning");
794 return;
795 }
796
797 showVideoUi(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700798 newVideoState,
799 call.getState(),
800 call.getVideoTech().getSessionModificationState(),
801 call.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800802
803 // Communicate the current camera to telephony and make a request for the camera
804 // capabilities.
805 if (videoCall != null) {
806 Surface surface = getRemoteVideoSurfaceTexture().getSavedSurface();
807 if (surface != null) {
808 LogUtil.v(
809 "VideoCallPresenter.adjustVideoMode", "calling setDisplaySurface with: " + surface);
810 videoCall.setDisplaySurface(surface);
811 }
812
813 Assert.checkState(
linyuh183cb712017-12-27 17:02:37 -0800814 deviceOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN);
815 videoCall.setDeviceOrientation(deviceOrientation);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700816 enableCamera(
roldenburgca475472017-10-25 13:00:42 -0700817 call, isCameraRequired(newVideoState, call.getVideoTech().getSessionModificationState()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800818 }
linyuh183cb712017-12-27 17:02:37 -0800819 int previousVideoState = currentVideoState;
820 currentVideoState = newVideoState;
821 isVideoMode = true;
Eric Erfanianccca3152017-02-22 16:32:36 -0800822
823 // adjustVideoMode may be called if we are already in a 1-way video state. In this case
824 // we do not want to trigger auto-fullscreen mode.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700825 if (!isVideoCall(previousVideoState) && isVideoCall(newVideoState)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800826 maybeAutoEnterFullscreen(call);
827 }
828 }
829
830 private static boolean shouldShowVideoUiForCall(@Nullable DialerCall call) {
831 if (call == null) {
832 return false;
833 }
834
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700835 if (isVideoCall(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800836 return true;
837 }
838
839 if (isVideoUpgrade(call)) {
840 return true;
841 }
842
843 return false;
844 }
845
roldenburgca475472017-10-25 13:00:42 -0700846 private void enableCamera(DialerCall call, boolean isCameraRequired) {
847 LogUtil.v("VideoCallPresenter.enableCamera", "call: %s, enabling: %b", call, isCameraRequired);
848 if (call == null) {
849 LogUtil.i("VideoCallPresenter.enableCamera", "call is null");
Eric Erfanianccca3152017-02-22 16:32:36 -0800850 return;
851 }
852
linyuh183cb712017-12-27 17:02:37 -0800853 boolean hasCameraPermission = VideoUtils.hasCameraPermissionAndShownPrivacyToast(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800854 if (!hasCameraPermission) {
roldenburgca475472017-10-25 13:00:42 -0700855 call.getVideoTech().setCamera(null);
linyuh183cb712017-12-27 17:02:37 -0800856 previewSurfaceState = PreviewSurfaceState.NONE;
Eric Erfanian938468d2017-10-24 14:05:52 -0700857 // TODO(wangqi): Inform remote party that the video is off. This is similar to a bug.
Eric Erfanianccca3152017-02-22 16:32:36 -0800858 } else if (isCameraRequired) {
859 InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
roldenburgca475472017-10-25 13:00:42 -0700860 call.getVideoTech().setCamera(cameraManager.getActiveCameraId());
linyuh183cb712017-12-27 17:02:37 -0800861 previewSurfaceState = PreviewSurfaceState.CAMERA_SET;
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 } else {
linyuh183cb712017-12-27 17:02:37 -0800863 previewSurfaceState = PreviewSurfaceState.NONE;
roldenburgca475472017-10-25 13:00:42 -0700864 call.getVideoTech().setCamera(null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800865 }
866 }
867
868 /** Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */
869 private void exitVideoMode() {
870 LogUtil.i("VideoCallPresenter.exitVideoMode", "");
871
872 showVideoUi(
873 VideoProfile.STATE_AUDIO_ONLY,
wangqibb94ca62018-04-27 14:34:04 -0700874 DialerCallState.ACTIVE,
Eric Erfanian90508232017-03-24 09:31:16 -0700875 SessionModificationState.NO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800876 false /* isRemotelyHeld */);
linyuh183cb712017-12-27 17:02:37 -0800877 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 InCallPresenter.getInstance().setFullScreen(false);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700879 InCallPresenter.getInstance().enableScreenTimeout(false);
linyuh183cb712017-12-27 17:02:37 -0800880 isVideoMode = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800881 }
882
883 /**
884 * Based on the current video state and call state, show or hide the incoming and outgoing video
885 * surfaces. The outgoing video surface is shown any time video is transmitting. The incoming
886 * video surface is shown whenever the video is un-paused and active.
887 *
888 * @param videoState The video state.
889 * @param callState The call state.
890 */
891 private void showVideoUi(
892 int videoState,
893 int callState,
894 @SessionModificationState int sessionModificationState,
895 boolean isRemotelyHeld) {
linyuh183cb712017-12-27 17:02:37 -0800896 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800897 LogUtil.e("VideoCallPresenter.showVideoUi", "videoCallScreen is null returning");
898 return;
899 }
900 boolean showIncomingVideo = showIncomingVideo(videoState, callState);
linyuh183cb712017-12-27 17:02:37 -0800901 boolean showOutgoingVideo = showOutgoingVideo(context, videoState, sessionModificationState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800902 LogUtil.i(
903 "VideoCallPresenter.showVideoUi",
904 "showIncoming: %b, showOutgoing: %b, isRemotelyHeld: %b",
905 showIncomingVideo,
906 showOutgoingVideo,
907 isRemotelyHeld);
908 updateRemoteVideoSurfaceDimensions();
linyuh183cb712017-12-27 17:02:37 -0800909 videoCallScreen.showVideoViews(showOutgoingVideo, showIncomingVideo, isRemotelyHeld);
Eric Erfanianccca3152017-02-22 16:32:36 -0800910
911 InCallPresenter.getInstance().enableScreenTimeout(VideoProfile.isAudioOnly(videoState));
912 updateFullscreenAndGreenScreenMode(callState, sessionModificationState);
913 }
914
915 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800916 * Handles peer video dimension changes.
917 *
918 * @param call The call which experienced a peer video dimension change.
919 * @param width The new peer video width .
920 * @param height The new peer video height.
921 */
922 @Override
923 public void onUpdatePeerDimensions(DialerCall call, int width, int height) {
924 LogUtil.i("VideoCallPresenter.onUpdatePeerDimensions", "width: %d, height: %d", width, height);
linyuh183cb712017-12-27 17:02:37 -0800925 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800926 LogUtil.e("VideoCallPresenter.onUpdatePeerDimensions", "videoCallScreen is null");
927 return;
928 }
linyuh183cb712017-12-27 17:02:37 -0800929 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800930 LogUtil.e(
931 "VideoCallPresenter.onUpdatePeerDimensions", "current call is not equal to primary");
932 return;
933 }
934
935 // Change size of display surface to match the peer aspect ratio
linyuh183cb712017-12-27 17:02:37 -0800936 if (width > 0 && height > 0 && videoCallScreen != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800937 getRemoteVideoSurfaceTexture().setSourceVideoDimensions(new Point(width, height));
linyuh183cb712017-12-27 17:02:37 -0800938 videoCallScreen.onRemoteVideoDimensionsChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800939 }
940 }
941
942 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800943 * Handles a change to the dimensions of the local camera. Receiving the camera capabilities
944 * triggers the creation of the video
945 *
946 * @param call The call which experienced the camera dimension change.
947 * @param width The new camera video width.
948 * @param height The new camera video height.
949 */
950 @Override
951 public void onCameraDimensionsChange(DialerCall call, int width, int height) {
952 LogUtil.i(
953 "VideoCallPresenter.onCameraDimensionsChange",
954 "call: %s, width: %d, height: %d",
955 call,
956 width,
957 height);
linyuh183cb712017-12-27 17:02:37 -0800958 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800959 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "ui is null");
960 return;
961 }
962
linyuh183cb712017-12-27 17:02:37 -0800963 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800964 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "not the primary call");
965 return;
966 }
967
linyuh183cb712017-12-27 17:02:37 -0800968 previewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
Eric Erfanianccca3152017-02-22 16:32:36 -0800969 changePreviewDimensions(width, height);
970
971 // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
972 // If it not yet ready, it will be set when when creation completes.
973 Surface surface = getLocalVideoSurfaceTexture().getSavedSurface();
974 if (surface != null) {
linyuh183cb712017-12-27 17:02:37 -0800975 previewSurfaceState = PreviewSurfaceState.SURFACE_SET;
976 videoCall.setPreviewSurface(surface);
Eric Erfanianccca3152017-02-22 16:32:36 -0800977 }
978 }
979
980 /**
981 * Changes the dimensions of the preview surface.
982 *
983 * @param width The new width.
984 * @param height The new height.
985 */
986 private void changePreviewDimensions(int width, int height) {
linyuh183cb712017-12-27 17:02:37 -0800987 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800988 return;
989 }
990
991 // Resize the surface used to display the preview video
992 getLocalVideoSurfaceTexture().setSurfaceDimensions(new Point(width, height));
linyuh183cb712017-12-27 17:02:37 -0800993 videoCallScreen.onLocalVideoDimensionsChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800994 }
995
996 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800997 * Handles changes to the device orientation.
998 *
999 * @param orientation The screen orientation of the device (one of: {@link
1000 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
1001 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
1002 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
1003 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
1004 */
1005 @Override
1006 public void onDeviceOrientationChanged(int orientation) {
1007 LogUtil.i(
1008 "VideoCallPresenter.onDeviceOrientationChanged",
1009 "orientation: %d -> %d",
linyuh183cb712017-12-27 17:02:37 -08001010 deviceOrientation,
Eric Erfanianccca3152017-02-22 16:32:36 -08001011 orientation);
linyuh183cb712017-12-27 17:02:37 -08001012 deviceOrientation = orientation;
Eric Erfanianccca3152017-02-22 16:32:36 -08001013
linyuh183cb712017-12-27 17:02:37 -08001014 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001015 LogUtil.e("VideoCallPresenter.onDeviceOrientationChanged", "videoCallScreen is null");
1016 return;
1017 }
1018
1019 Point previewDimensions = getLocalVideoSurfaceTexture().getSurfaceDimensions();
1020 if (previewDimensions == null) {
1021 return;
1022 }
1023 LogUtil.v(
1024 "VideoCallPresenter.onDeviceOrientationChanged",
1025 "orientation: %d, size: %s",
1026 orientation,
1027 previewDimensions);
1028 changePreviewDimensions(previewDimensions.x, previewDimensions.y);
1029
linyuh183cb712017-12-27 17:02:37 -08001030 videoCallScreen.onLocalVideoOrientationChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001031 }
1032
1033 /**
1034 * Exits fullscreen mode if the current call context has changed to a non-video call.
1035 *
1036 * @param call The call.
1037 */
1038 protected void maybeExitFullscreen(DialerCall call) {
1039 if (call == null) {
1040 return;
1041 }
1042
wangqibb94ca62018-04-27 14:34:04 -07001043 if (!isVideoCall(call) || call.getState() == DialerCallState.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001044 LogUtil.i("VideoCallPresenter.maybeExitFullscreen", "exiting fullscreen");
1045 InCallPresenter.getInstance().setFullScreen(false);
1046 }
1047 }
1048
1049 /**
1050 * Schedules auto-entering of fullscreen mode. Will not enter full screen mode if any of the
1051 * following conditions are met: 1. No call 2. DialerCall is not active 3. The current video state
1052 * is not bi-directional. 4. Already in fullscreen mode 5. In accessibility mode
1053 *
1054 * @param call The current call.
1055 */
1056 protected void maybeAutoEnterFullscreen(DialerCall call) {
linyuh183cb712017-12-27 17:02:37 -08001057 if (!isAutoFullscreenEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001058 return;
1059 }
1060
1061 if (call == null
wangqibb94ca62018-04-27 14:34:04 -07001062 || call.getState() != DialerCallState.ACTIVE
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001063 || !isBidirectionalVideoCall(call)
Eric Erfanianccca3152017-02-22 16:32:36 -08001064 || InCallPresenter.getInstance().isFullscreen()
linyuh183cb712017-12-27 17:02:37 -08001065 || (context != null && AccessibilityUtil.isTouchExplorationEnabled(context))) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001066 // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
1067 cancelAutoFullScreen();
1068 return;
1069 }
1070
linyuh183cb712017-12-27 17:02:37 -08001071 if (autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001072 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "already pending.");
1073 return;
1074 }
1075 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "scheduled");
linyuh183cb712017-12-27 17:02:37 -08001076 autoFullScreenPending = true;
1077 handler.removeCallbacks(autoFullscreenRunnable);
1078 handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis);
Eric Erfanianccca3152017-02-22 16:32:36 -08001079 }
1080
1081 /** Cancels pending auto fullscreen mode. */
1082 @Override
1083 public void cancelAutoFullScreen() {
linyuh183cb712017-12-27 17:02:37 -08001084 if (!autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001085 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "none pending.");
1086 return;
1087 }
1088 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "cancelling pending");
linyuh183cb712017-12-27 17:02:37 -08001089 autoFullScreenPending = false;
1090 handler.removeCallbacks(autoFullscreenRunnable);
Eric Erfanianccca3152017-02-22 16:32:36 -08001091 }
1092
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001093 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001094 public boolean shouldShowCameraPermissionToast() {
linyuh183cb712017-12-27 17:02:37 -08001095 if (primaryCall == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001096 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001097 return false;
1098 }
linyuh183cb712017-12-27 17:02:37 -08001099 if (primaryCall.didShowCameraPermission()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001100 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001101 "VideoCallPresenter.shouldShowCameraPermissionToast", "already shown for this call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001102 return false;
1103 }
zachh190343a2018-05-31 17:30:46 -07001104 if (!ConfigProviderComponent.get(context)
1105 .getConfigProvider()
1106 .getBoolean("camera_permission_dialog_allowed", true)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001107 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "disabled by config");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001108 return false;
1109 }
linyuh183cb712017-12-27 17:02:37 -08001110 return !VideoUtils.hasCameraPermission(context)
1111 || !PermissionsUtil.hasCameraPrivacyToastShown(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001112 }
1113
1114 @Override
1115 public void onCameraPermissionDialogShown() {
linyuh183cb712017-12-27 17:02:37 -08001116 if (primaryCall != null) {
1117 primaryCall.setDidShowCameraPermission(true);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001118 }
1119 }
1120
Eric Erfanianccca3152017-02-22 16:32:36 -08001121 private void updateRemoteVideoSurfaceDimensions() {
linyuh183cb712017-12-27 17:02:37 -08001122 Activity activity = videoCallScreen.getVideoCallScreenFragment().getActivity();
Eric Erfanianccca3152017-02-22 16:32:36 -08001123 if (activity != null) {
1124 Point screenSize = new Point();
1125 activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
1126 getRemoteVideoSurfaceTexture().setSurfaceDimensions(screenSize);
1127 }
1128 }
1129
1130 private static boolean isVideoUpgrade(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001131 return call != null
1132 && (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
Eric Erfanianccca3152017-02-22 16:32:36 -08001133 }
1134
1135 private static boolean isVideoUpgrade(@SessionModificationState int state) {
1136 return VideoUtils.hasSentVideoUpgradeRequest(state)
1137 || VideoUtils.hasReceivedVideoUpgradeRequest(state);
1138 }
1139
wangqida410d32018-03-06 16:51:38 -08001140 @Override
1141 public void onIncomingCall(DialerCall call) {}
1142
1143 @Override
1144 public void onUpgradeToVideo(DialerCall call) {}
1145
1146 @Override
1147 public void onSessionModificationStateChange(DialerCall call) {}
1148
1149 @Override
1150 public void onCallListChange(CallList callList) {}
1151
1152 @Override
1153 public void onDisconnect(DialerCall call) {}
1154
1155 @Override
1156 public void onWiFiToLteHandover(DialerCall call) {
1157 if (call.isVideoCall() || call.hasSentVideoUpgradeRequest()) {
1158 videoCallScreen.onHandoverFromWiFiToLte();
1159 }
1160 }
1161
1162 @Override
1163 public void onHandoverToWifiFailed(DialerCall call) {}
1164
1165 @Override
1166 public void onInternationalCallOnWifi(@NonNull DialerCall call) {}
1167
Eric Erfanianccca3152017-02-22 16:32:36 -08001168 private class LocalDelegate implements VideoSurfaceDelegate {
1169 @Override
1170 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001171 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001172 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no UI");
1173 return;
1174 }
linyuh183cb712017-12-27 17:02:37 -08001175 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001176 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no video call");
1177 return;
1178 }
1179
1180 // If the preview surface has just been created and we have already received camera
1181 // capabilities, but not yet set the surface, we will set the surface now.
linyuh183cb712017-12-27 17:02:37 -08001182 if (previewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
1183 previewSurfaceState = PreviewSurfaceState.SURFACE_SET;
1184 videoCall.setPreviewSurface(videoCallSurface.getSavedSurface());
1185 } else if (previewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
1186 enableCamera(primaryCall, true);
Eric Erfanianccca3152017-02-22 16:32:36 -08001187 }
1188 }
1189
1190 @Override
1191 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001192 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001193 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceReleased", "no video call");
1194 return;
1195 }
1196
linyuh183cb712017-12-27 17:02:37 -08001197 videoCall.setPreviewSurface(null);
1198 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -08001199 }
1200
1201 @Override
1202 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001203 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001204 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", "no video call");
1205 return;
1206 }
1207
1208 boolean isChangingConfigurations = InCallPresenter.getInstance().isChangingConfigurations();
1209 if (!isChangingConfigurations) {
linyuh183cb712017-12-27 17:02:37 -08001210 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -08001211 } else {
1212 LogUtil.i(
1213 "VideoCallPresenter.LocalDelegate.onSurfaceDestroyed",
1214 "activity is being destroyed due to configuration changes. Not closing the camera.");
1215 }
1216 }
1217
1218 @Override
1219 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1220 VideoCallPresenter.this.onSurfaceClick();
1221 }
1222 }
1223
1224 private class RemoteDelegate implements VideoSurfaceDelegate {
1225 @Override
1226 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001227 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001228 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no UI");
1229 return;
1230 }
linyuh183cb712017-12-27 17:02:37 -08001231 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001232 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no video call");
1233 return;
1234 }
linyuh183cb712017-12-27 17:02:37 -08001235 videoCall.setDisplaySurface(videoCallSurface.getSavedSurface());
Eric Erfanianccca3152017-02-22 16:32:36 -08001236 }
1237
1238 @Override
1239 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001240 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001241 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceReleased", "no video call");
1242 return;
1243 }
linyuh183cb712017-12-27 17:02:37 -08001244 videoCall.setDisplaySurface(null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001245 }
1246
1247 @Override
1248 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {}
1249
1250 @Override
1251 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1252 VideoCallPresenter.this.onSurfaceClick();
1253 }
1254 }
1255
1256 /** Defines the state of the preview surface negotiation with the telephony layer. */
1257 private static class PreviewSurfaceState {
1258
1259 /**
1260 * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet started.
1261 */
1262 private static final int NONE = 0;
1263
1264 /**
1265 * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet been
1266 * received.
1267 */
1268 private static final int CAMERA_SET = 1;
1269
1270 /**
1271 * The camera capabilties have been received from telephony, but the surface has not yet been
1272 * set on the {@link VideoCall}.
1273 */
1274 private static final int CAPABILITIES_RECEIVED = 2;
1275
1276 /** The surface has been set on the {@link VideoCall}. */
1277 private static final int SURFACE_SET = 3;
1278 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001279
1280 private static boolean isBidirectionalVideoCall(DialerCall call) {
linyuh122fb0b2018-03-26 13:35:32 -07001281 return VideoProfile.isBidirectional(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001282 }
1283
1284 private static boolean isIncomingVideoCall(DialerCall call) {
1285 if (!isVideoCall(call)) {
1286 return false;
1287 }
1288 final int state = call.getState();
wangqibb94ca62018-04-27 14:34:04 -07001289 return (state == DialerCallState.INCOMING) || (state == DialerCallState.CALL_WAITING);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001290 }
1291
1292 private static boolean isActiveVideoCall(DialerCall call) {
wangqibb94ca62018-04-27 14:34:04 -07001293 return isVideoCall(call) && call.getState() == DialerCallState.ACTIVE;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001294 }
1295
1296 private static boolean isOutgoingVideoCall(DialerCall call) {
1297 if (!isVideoCall(call)) {
1298 return false;
1299 }
1300 final int state = call.getState();
wangqibb94ca62018-04-27 14:34:04 -07001301 return DialerCallState.isDialing(state)
1302 || state == DialerCallState.CONNECTING
1303 || state == DialerCallState.SELECT_PHONE_ACCOUNT;
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001304 }
1305
1306 private static boolean isAudioCall(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001307 return call != null && VideoProfile.isAudioOnly(call.getVideoState());
1308 }
1309
1310 private static boolean isVideoCall(@Nullable DialerCall call) {
1311 return call != null && call.isVideoCall();
1312 }
1313
1314 private static boolean isVideoCall(int videoState) {
linyuh122fb0b2018-03-26 13:35:32 -07001315 return VideoProfile.isTransmissionEnabled(videoState)
1316 || VideoProfile.isReceptionEnabled(videoState);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001317 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001318}