blob: 31999ef2e1238d370d4717b722dc1163e52082e3 [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;
23import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080024import android.telecom.InCallService.VideoCall;
25import android.telecom.VideoProfile;
26import android.telecom.VideoProfile.CameraCapabilities;
27import android.view.Surface;
Eric Erfanian90508232017-03-24 09:31:16 -070028import android.view.SurfaceView;
Eric Erfanianccca3152017-02-22 16:32:36 -080029import com.android.dialer.common.Assert;
30import com.android.dialer.common.ConfigProviderBindings;
31import com.android.dialer.common.LogUtil;
32import com.android.dialer.compat.CompatUtils;
33import com.android.incallui.InCallPresenter.InCallDetailsListener;
34import com.android.incallui.InCallPresenter.InCallOrientationListener;
35import com.android.incallui.InCallPresenter.InCallStateListener;
36import com.android.incallui.InCallPresenter.IncomingCallListener;
37import com.android.incallui.call.CallList;
38import com.android.incallui.call.DialerCall;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070039import com.android.incallui.call.DialerCall.CameraDirection;
Eric Erfanianccca3152017-02-22 16:32:36 -080040import com.android.incallui.call.DialerCall.State;
41import com.android.incallui.call.InCallVideoCallCallbackNotifier;
42import com.android.incallui.call.InCallVideoCallCallbackNotifier.SurfaceChangeListener;
Eric Erfanianccca3152017-02-22 16:32:36 -080043import com.android.incallui.util.AccessibilityUtil;
44import com.android.incallui.video.protocol.VideoCallScreen;
45import com.android.incallui.video.protocol.VideoCallScreenDelegate;
46import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate;
47import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
Eric Erfanian90508232017-03-24 09:31:16 -070048import com.android.incallui.videotech.utils.SessionModificationState;
49import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080050import java.util.Objects;
51
52/**
53 * Logic related to the {@link VideoCallScreen} and for managing changes to the video calling
54 * surfaces based on other user interface events and incoming events from the {@class
55 * VideoCallListener}.
56 *
57 * <p>When a call's video state changes to bi-directional video, the {@link
58 * com.android.incallui.VideoCallPresenter} performs the following negotiation with the telephony
59 * layer:
60 *
61 * <ul>
62 * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.
63 * <li>{@code VideoCallPresenter} creates the preview surface.
64 * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.
65 * <li>Telephony layer sends {@link CameraCapabilities}, including the dimensions of the video for
66 * the current camera.
67 * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect ratio of
68 * the camera.
69 * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.
70 * </ul>
71 *
72 * <p>When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both
73 * surfaces.
74 */
75public class VideoCallPresenter
76 implements IncomingCallListener,
77 InCallOrientationListener,
78 InCallStateListener,
79 InCallDetailsListener,
80 SurfaceChangeListener,
Eric Erfanianccca3152017-02-22 16:32:36 -080081 InCallPresenter.InCallEventListener,
82 VideoCallScreenDelegate {
83
84 private static boolean mIsVideoMode = false;
85
86 private final Handler mHandler = new Handler();
87 private VideoCallScreen mVideoCallScreen;
88
89 /** The current context. */
90 private Context mContext;
91
Eric Erfanianccca3152017-02-22 16:32:36 -080092 /** The call the video surfaces are currently related to */
93 private DialerCall mPrimaryCall;
94 /**
95 * The {@link VideoCall} used to inform the video telephony layer of changes to the video
96 * surfaces.
97 */
98 private VideoCall mVideoCall;
99 /** Determines if the current UI state represents a video call. */
100 private int mCurrentVideoState;
101 /** DialerCall's current state */
102 private int mCurrentCallState = DialerCall.State.INVALID;
103 /** Determines the device orientation (portrait/lanscape). */
104 private int mDeviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN;
105 /** Tracks the state of the preview surface negotiation with the telephony layer. */
106 private int mPreviewSurfaceState = PreviewSurfaceState.NONE;
107 /**
108 * Determines whether video calls should automatically enter full screen mode after {@link
109 * #mAutoFullscreenTimeoutMillis} milliseconds.
110 */
111 private boolean mIsAutoFullscreenEnabled = false;
112 /**
113 * Determines the number of milliseconds after which a video call will automatically enter
114 * fullscreen mode. Requires {@link #mIsAutoFullscreenEnabled} to be {@code true}.
115 */
116 private int mAutoFullscreenTimeoutMillis = 0;
117 /**
118 * Determines if the countdown is currently running to automatically enter full screen video mode.
119 */
120 private boolean mAutoFullScreenPending = false;
121 /** Whether if the call is remotely held. */
122 private boolean mIsRemotelyHeld = false;
123 /**
124 * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto
125 * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit the
126 * dialpad).
127 */
128 private Runnable mAutoFullscreenRunnable =
129 new Runnable() {
130 @Override
131 public void run() {
132 if (mAutoFullScreenPending
133 && !InCallPresenter.getInstance().isDialpadVisible()
134 && mIsVideoMode) {
135
136 LogUtil.v("VideoCallPresenter.mAutoFullScreenRunnable", "entering fullscreen mode");
137 InCallPresenter.getInstance().setFullScreen(true);
138 mAutoFullScreenPending = false;
139 } else {
140 LogUtil.v(
141 "VideoCallPresenter.mAutoFullScreenRunnable",
142 "skipping scheduled fullscreen mode.");
143 }
144 }
145 };
146
147 private boolean isVideoCallScreenUiReady;
148
149 private static boolean isCameraRequired(int videoState, int sessionModificationState) {
150 return VideoProfile.isBidirectional(videoState)
151 || VideoProfile.isTransmissionEnabled(videoState)
152 || isVideoUpgrade(sessionModificationState);
153 }
154
155 /**
156 * Determines if the incoming video surface should be shown based on the current videoState and
Eric Erfaniand8046e52017-04-06 09:41:50 -0700157 * callState. The video surface is shown when incoming video is not paused, the call is active or
158 * dialing and video reception is enabled.
Eric Erfanianccca3152017-02-22 16:32:36 -0800159 *
160 * @param videoState The current video state.
161 * @param callState The current call state.
162 * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise.
163 */
164 public static boolean showIncomingVideo(int videoState, int callState) {
165 if (!CompatUtils.isVideoCompatible()) {
166 return false;
167 }
168
169 boolean isPaused = VideoProfile.isPaused(videoState);
170 boolean isCallActive = callState == DialerCall.State.ACTIVE;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700171 //Show incoming Video for dialing calls to support early media
172 boolean isCallOutgoingPending =
173 DialerCall.State.isDialing(callState) || callState == DialerCall.State.CONNECTING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800174
Eric Erfaniand8046e52017-04-06 09:41:50 -0700175 return !isPaused
176 && (isCallActive || isCallOutgoingPending)
177 && VideoProfile.isReceptionEnabled(videoState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800178 }
179
180 /**
181 * Determines if the outgoing video surface should be shown based on the current videoState. The
182 * video surface is shown if video transmission is enabled.
183 *
184 * @return {@code true} if the the outgoing video surface should be shown, {@code false}
185 * otherwise.
186 */
187 public static boolean showOutgoingVideo(
188 Context context, int videoState, int sessionModificationState) {
189 if (!VideoUtils.hasCameraPermissionAndAllowedByUser(context)) {
190 LogUtil.i("VideoCallPresenter.showOutgoingVideo", "Camera permission is disabled by user.");
191 return false;
192 }
193
194 if (!CompatUtils.isVideoCompatible()) {
195 return false;
196 }
197
198 return VideoProfile.isTransmissionEnabled(videoState)
199 || isVideoUpgrade(sessionModificationState);
200 }
201
202 private static void updateCameraSelection(DialerCall call) {
203 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + call);
204 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + toSimpleString(call));
205
206 final DialerCall activeCall = CallList.getInstance().getActiveCall();
207 int cameraDir;
208
209 // this function should never be called with null call object, however if it happens we
210 // should handle it gracefully.
211 if (call == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700212 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800213 LogUtil.e(
214 "VideoCallPresenter.updateCameraSelection",
215 "call is null. Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
216 }
217
218 // Clear camera direction if this is not a video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700219 else if (isAudioCall(call) && !isVideoUpgrade(call)) {
220 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
221 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800222 }
223
224 // If this is a waiting video call, default to active call's camera,
225 // since we don't want to change the current camera for waiting call
226 // without user's permission.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700227 else if (isVideoCall(activeCall) && isIncomingVideoCall(call)) {
228 cameraDir = activeCall.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800229 }
230
231 // Infer the camera direction from the video state and store it,
232 // if this is an outgoing video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700233 else if (isOutgoingVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700235 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800236 }
237
238 // Use the stored camera dir if this is an outgoing video call for which camera direction
239 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700240 else if (isOutgoingVideoCall(call)) {
241 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800242 }
243
244 // Infer the camera direction from the video state and store it,
245 // if this is an active video call and camera direction is not set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700246 else if (isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800247 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700248 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800249 }
250
251 // Use the stored camera dir if this is an active video call for which camera direction
252 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700253 else if (isActiveVideoCall(call)) {
254 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800255 }
256
257 // For all other cases infer the camera direction but don't store it in the call object.
258 else {
259 cameraDir = toCameraDirection(call.getVideoState());
260 }
261
262 LogUtil.i(
263 "VideoCallPresenter.updateCameraSelection",
264 "setting camera direction to %d, call: %s",
265 cameraDir,
266 call);
267 final InCallCameraManager cameraManager =
268 InCallPresenter.getInstance().getInCallCameraManager();
269 cameraManager.setUseFrontFacingCamera(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700270 cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800271 }
272
273 private static int toCameraDirection(int videoState) {
274 return VideoProfile.isTransmissionEnabled(videoState)
275 && !VideoProfile.isBidirectional(videoState)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700276 ? CameraDirection.CAMERA_DIRECTION_BACK_FACING
277 : CameraDirection.CAMERA_DIRECTION_FRONT_FACING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800278 }
279
280 private static boolean isCameraDirectionSet(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700281 return isVideoCall(call) && call.getCameraDir() != CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800282 }
283
284 private static String toSimpleString(DialerCall call) {
285 return call == null ? null : call.toSimpleString();
286 }
287
288 /**
289 * Initializes the presenter.
290 *
291 * @param context The current context.
292 */
293 @Override
294 public void initVideoCallScreenDelegate(Context context, VideoCallScreen videoCallScreen) {
295 mContext = context;
296 mVideoCallScreen = videoCallScreen;
297 mIsAutoFullscreenEnabled =
298 mContext.getResources().getBoolean(R.bool.video_call_auto_fullscreen);
299 mAutoFullscreenTimeoutMillis =
300 mContext.getResources().getInteger(R.integer.video_call_auto_fullscreen_timeout);
301 }
302
303 /** Called when the user interface is ready to be used. */
304 @Override
305 public void onVideoCallScreenUiReady() {
306 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiReady", "");
307 Assert.checkState(!isVideoCallScreenUiReady);
308
309 // Do not register any listeners if video calling is not compatible to safeguard against
310 // any accidental calls of video calling code.
311 if (!CompatUtils.isVideoCompatible()) {
312 return;
313 }
314
315 mDeviceOrientation = InCallOrientationEventListener.getCurrentOrientation();
316
317 // Register for call state changes last
318 InCallPresenter.getInstance().addListener(this);
319 InCallPresenter.getInstance().addDetailsListener(this);
320 InCallPresenter.getInstance().addIncomingCallListener(this);
321 InCallPresenter.getInstance().addOrientationListener(this);
322 // To get updates of video call details changes
323 InCallPresenter.getInstance().addInCallEventListener(this);
324 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate());
325 InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate());
326
327 // Register for surface and video events from {@link InCallVideoCallListener}s.
328 InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800329 mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
330 mCurrentCallState = DialerCall.State.INVALID;
331
332 InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState();
333 onStateChange(inCallState, inCallState, CallList.getInstance());
334 isVideoCallScreenUiReady = true;
335 }
336
337 /** Called when the user interface is no longer ready to be used. */
338 @Override
339 public void onVideoCallScreenUiUnready() {
340 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiUnready", "");
341 Assert.checkState(isVideoCallScreenUiReady);
342
343 if (!CompatUtils.isVideoCompatible()) {
344 return;
345 }
346
347 cancelAutoFullScreen();
348
349 InCallPresenter.getInstance().removeListener(this);
350 InCallPresenter.getInstance().removeDetailsListener(this);
351 InCallPresenter.getInstance().removeIncomingCallListener(this);
352 InCallPresenter.getInstance().removeOrientationListener(this);
353 InCallPresenter.getInstance().removeInCallEventListener(this);
354 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null);
355
356 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
360 // we won't get any more call state changes. See b/32957114.
361 if (mPrimaryCall != null) {
362 updateCameraSelection(mPrimaryCall);
363 }
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);
378 maybeAutoEnterFullscreen(mPrimaryCall);
379 // 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);
393 maybeAutoEnterFullscreen(mPrimaryCall);
394 }
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() {
414 return mDeviceOrientation;
415 }
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", "");
424 VideoUtils.setCameraAllowedByUser(mContext);
425 enableCamera(mPrimaryCall.getVideoCall(), isCameraRequired());
426 showVideoUi(
427 mPrimaryCall.getVideoState(),
428 mPrimaryCall.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700429 mPrimaryCall.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 mPrimaryCall.isRemotelyHeld());
431 InCallPresenter.getInstance().getInCallCameraManager().onCameraPermissionGranted();
432 }
433
434 /**
435 * Called when the user interacts with the UI. If a fullscreen timer is pending then we start the
436 * timer from scratch to avoid having the UI disappear while the user is interacting with it.
437 */
438 @Override
439 public void resetAutoFullscreenTimer() {
440 if (mAutoFullScreenPending) {
441 LogUtil.i("VideoCallPresenter.resetAutoFullscreenTimer", "resetting");
442 mHandler.removeCallbacks(mAutoFullscreenRunnable);
443 mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis);
444 }
445 }
446
447 /**
448 * Handles incoming calls.
449 *
450 * @param oldState The old in call state.
451 * @param newState The new in call state.
452 * @param call The call.
453 */
454 @Override
455 public void onIncomingCall(
456 InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, DialerCall call) {
457 // same logic should happen as with onStateChange()
458 onStateChange(oldState, newState, CallList.getInstance());
459 }
460
461 /**
462 * Handles state changes (including incoming calls)
463 *
464 * @param newState The in call state.
465 * @param callList The call list.
466 */
467 @Override
468 public void onStateChange(
469 InCallPresenter.InCallState oldState,
470 InCallPresenter.InCallState newState,
471 CallList callList) {
472 LogUtil.v(
473 "VideoCallPresenter.onStateChange",
474 "oldState: %s, newState: %s, isVideoMode: %b",
475 oldState,
476 newState,
477 isVideoMode());
478
479 if (newState == InCallPresenter.InCallState.NO_CALLS) {
480 if (isVideoMode()) {
481 exitVideoMode();
482 }
483
484 InCallPresenter.getInstance().cleanupSurfaces();
485 }
486
487 // Determine the primary active call).
488 DialerCall primary = null;
489
490 // Determine the call which is the focus of the user's attention. In the case of an
491 // incoming call waiting call, the primary call is still the active video call, however
492 // the determination of whether we should be in fullscreen mode is based on the type of the
493 // incoming call, not the active video call.
494 DialerCall currentCall = null;
495
496 if (newState == InCallPresenter.InCallState.INCOMING) {
497 // We don't want to replace active video call (primary call)
498 // with a waiting call, since user may choose to ignore/decline the waiting call and
499 // this should have no impact on current active video call, that is, we should not
500 // change the camera or UI unless the waiting VT call becomes active.
501 primary = callList.getActiveCall();
502 currentCall = callList.getIncomingCall();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700503 if (!isActiveVideoCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800504 primary = callList.getIncomingCall();
505 }
506 } else if (newState == InCallPresenter.InCallState.OUTGOING) {
507 currentCall = primary = callList.getOutgoingCall();
508 } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
509 currentCall = primary = callList.getPendingOutgoingCall();
510 } else if (newState == InCallPresenter.InCallState.INCALL) {
511 currentCall = primary = callList.getActiveCall();
512 }
513
514 final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary);
515 LogUtil.i(
516 "VideoCallPresenter.onStateChange",
517 "primaryChanged: %b, primary: %s, mPrimaryCall: %s",
518 primaryChanged,
519 primary,
520 mPrimaryCall);
521 if (primaryChanged) {
522 onPrimaryCallChanged(primary);
523 } else if (mPrimaryCall != null) {
524 updateVideoCall(primary);
525 }
526 updateCallCache(primary);
527
528 // If the call context changed, potentially exit fullscreen or schedule auto enter of
529 // fullscreen mode.
530 // If the current call context is no longer a video call, exit fullscreen mode.
531 maybeExitFullscreen(currentCall);
532 // Schedule auto-enter of fullscreen mode if the current call context is a video call
533 maybeAutoEnterFullscreen(currentCall);
534 }
535
536 /**
537 * Handles a change to the fullscreen mode of the app.
538 *
539 * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise.
540 */
541 @Override
542 public void onFullscreenModeChanged(boolean isFullscreenMode) {
543 cancelAutoFullScreen();
544 if (mPrimaryCall != null) {
545 updateFullscreenAndGreenScreenMode(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700546 mPrimaryCall.getState(), mPrimaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800547 } else {
Eric Erfanian90508232017-03-24 09:31:16 -0700548 updateFullscreenAndGreenScreenMode(State.INVALID, SessionModificationState.NO_REQUEST);
Eric Erfanianccca3152017-02-22 16:32:36 -0800549 }
550 }
551
552 private void checkForVideoStateChange(DialerCall call) {
553 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
554 final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
555
556 LogUtil.v(
557 "VideoCallPresenter.checkForVideoStateChange",
558 "shouldShowVideoUi: %b, hasVideoStateChanged: %b, isVideoMode: %b, previousVideoState: %s,"
559 + " newVideoState: %s",
560 shouldShowVideoUi,
561 hasVideoStateChanged,
562 isVideoMode(),
563 VideoProfile.videoStateToString(mCurrentVideoState),
564 VideoProfile.videoStateToString(call.getVideoState()));
565 if (!hasVideoStateChanged) {
566 return;
567 }
568
569 updateCameraSelection(call);
570
571 if (shouldShowVideoUi) {
572 adjustVideoMode(call);
573 } else if (isVideoMode()) {
574 exitVideoMode();
575 }
576 }
577
578 private void checkForCallStateChange(DialerCall call) {
579 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
580 final boolean hasCallStateChanged =
581 mCurrentCallState != call.getState() || mIsRemotelyHeld != call.isRemotelyHeld();
582 mIsRemotelyHeld = call.isRemotelyHeld();
583
584 LogUtil.v(
585 "VideoCallPresenter.checkForCallStateChange",
586 "shouldShowVideoUi: %b, hasCallStateChanged: %b, isVideoMode: %b",
587 shouldShowVideoUi,
588 hasCallStateChanged,
589 isVideoMode());
590
591 if (!hasCallStateChanged) {
592 return;
593 }
594
595 if (shouldShowVideoUi) {
596 final InCallCameraManager cameraManager =
597 InCallPresenter.getInstance().getInCallCameraManager();
598
599 String prevCameraId = cameraManager.getActiveCameraId();
600 updateCameraSelection(call);
601 String newCameraId = cameraManager.getActiveCameraId();
602
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700603 if (!Objects.equals(prevCameraId, newCameraId) && isActiveVideoCall(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800604 enableCamera(call.getVideoCall(), true);
605 }
606 }
607
608 // Make sure we hide or show the video UI if needed.
609 showVideoUi(
610 call.getVideoState(),
611 call.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700612 call.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 call.isRemotelyHeld());
614 }
615
616 private void onPrimaryCallChanged(DialerCall newPrimaryCall) {
617 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(newPrimaryCall);
618 final boolean isVideoMode = isVideoMode();
619
620 LogUtil.v(
621 "VideoCallPresenter.onPrimaryCallChanged",
622 "shouldShowVideoUi: %b, isVideoMode: %b",
623 shouldShowVideoUi,
624 isVideoMode);
625
626 if (!shouldShowVideoUi && isVideoMode) {
627 // Terminate video mode if new primary call is not a video call
628 // and we are currently in video mode.
629 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "exiting video mode...");
630 exitVideoMode();
631 } else if (shouldShowVideoUi) {
632 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "entering video mode...");
633
634 updateCameraSelection(newPrimaryCall);
635 adjustVideoMode(newPrimaryCall);
636 }
637 checkForOrientationAllowedChange(newPrimaryCall);
638 }
639
640 private boolean isVideoMode() {
641 return mIsVideoMode;
642 }
643
644 private void updateCallCache(DialerCall call) {
645 if (call == null) {
646 mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
647 mCurrentCallState = DialerCall.State.INVALID;
648 mVideoCall = null;
649 mPrimaryCall = null;
650 } else {
651 mCurrentVideoState = call.getVideoState();
652 mVideoCall = call.getVideoCall();
653 mCurrentCallState = call.getState();
654 mPrimaryCall = call;
655 }
656 }
657
658 /**
659 * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in
660 * changes to the video state.
661 *
662 * @param call The call for which the details changed.
663 * @param details The new call details.
664 */
665 @Override
666 public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) {
667 LogUtil.v(
668 "VideoCallPresenter.onDetailsChanged",
669 "call: %s, details: %s, mPrimaryCall: %s",
670 call,
671 details,
672 mPrimaryCall);
673 if (call == null) {
674 return;
675 }
676 // If the details change is not for the currently active call no update is required.
677 if (!call.equals(mPrimaryCall)) {
678 LogUtil.v("VideoCallPresenter.onDetailsChanged", "details not for current active call");
679 return;
680 }
681
682 updateVideoCall(call);
683
684 updateCallCache(call);
685 }
686
687 private void updateVideoCall(DialerCall call) {
688 checkForVideoCallChange(call);
689 checkForVideoStateChange(call);
690 checkForCallStateChange(call);
691 checkForOrientationAllowedChange(call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700692 updateFullscreenAndGreenScreenMode(
693 call.getState(), call.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800694 }
695
696 private void checkForOrientationAllowedChange(@Nullable DialerCall call) {
697 InCallPresenter.getInstance()
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700698 .setInCallAllowsOrientationChange(isVideoCall(call) || isVideoUpgrade(call));
Eric Erfanianccca3152017-02-22 16:32:36 -0800699 }
700
701 private void updateFullscreenAndGreenScreenMode(
702 int callState, @SessionModificationState int sessionModificationState) {
703 if (mVideoCallScreen != null) {
704 boolean shouldShowFullscreen = InCallPresenter.getInstance().isFullscreen();
705 boolean shouldShowGreenScreen =
706 callState == State.DIALING
707 || callState == State.CONNECTING
708 || callState == State.INCOMING
709 || isVideoUpgrade(sessionModificationState);
710 mVideoCallScreen.updateFullscreenAndGreenScreenMode(
711 shouldShowFullscreen, shouldShowGreenScreen);
712 }
713 }
714
715 /** Checks for a change to the video call and changes it if required. */
716 private void checkForVideoCallChange(DialerCall call) {
717 final VideoCall videoCall = call.getVideoCall();
718 LogUtil.v(
719 "VideoCallPresenter.checkForVideoCallChange",
720 "videoCall: %s, mVideoCall: %s",
721 videoCall,
722 mVideoCall);
723 if (!Objects.equals(videoCall, mVideoCall)) {
724 changeVideoCall(call);
725 }
726 }
727
728 /**
729 * Handles a change to the video call. Sets the surfaces on the previous call to null and sets the
730 * surfaces on the new video call accordingly.
731 *
732 * @param call The new video call.
733 */
734 private void changeVideoCall(DialerCall call) {
735 final VideoCall videoCall = call == null ? null : call.getVideoCall();
736 LogUtil.i(
737 "VideoCallPresenter.changeVideoCall",
738 "videoCall: %s, mVideoCall: %s",
739 videoCall,
740 mVideoCall);
741 final boolean hasChanged = mVideoCall == null && videoCall != null;
742
743 mVideoCall = videoCall;
744 if (mVideoCall == null) {
745 LogUtil.v("VideoCallPresenter.changeVideoCall", "video call or primary call is null. Return");
746 return;
747 }
748
749 if (shouldShowVideoUiForCall(call) && hasChanged) {
750 adjustVideoMode(call);
751 }
752 }
753
754 private boolean isCameraRequired() {
755 return mPrimaryCall != null
756 && isCameraRequired(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700757 mPrimaryCall.getVideoState(),
758 mPrimaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800759 }
760
761 /**
762 * Adjusts the current video mode by setting up the preview and display surfaces as necessary.
763 * Expected to be called whenever the video state associated with a call changes (e.g. a user
764 * turns their camera on or off) to ensure the correct surfaces are shown/hidden. TODO: Need
765 * to adjust size and orientation of preview surface here.
766 */
767 private void adjustVideoMode(DialerCall call) {
768 VideoCall videoCall = call.getVideoCall();
769 int newVideoState = call.getVideoState();
770
771 LogUtil.i(
772 "VideoCallPresenter.adjustVideoMode",
773 "videoCall: %s, videoState: %d",
774 videoCall,
775 newVideoState);
776 if (mVideoCallScreen == null) {
777 LogUtil.e("VideoCallPresenter.adjustVideoMode", "error VideoCallScreen is null so returning");
778 return;
779 }
780
781 showVideoUi(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700782 newVideoState,
783 call.getState(),
784 call.getVideoTech().getSessionModificationState(),
785 call.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800786
787 // Communicate the current camera to telephony and make a request for the camera
788 // capabilities.
789 if (videoCall != null) {
790 Surface surface = getRemoteVideoSurfaceTexture().getSavedSurface();
791 if (surface != null) {
792 LogUtil.v(
793 "VideoCallPresenter.adjustVideoMode", "calling setDisplaySurface with: " + surface);
794 videoCall.setDisplaySurface(surface);
795 }
796
797 Assert.checkState(
798 mDeviceOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN);
799 videoCall.setDeviceOrientation(mDeviceOrientation);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700800 enableCamera(
801 videoCall,
802 isCameraRequired(newVideoState, call.getVideoTech().getSessionModificationState()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800803 }
804 int previousVideoState = mCurrentVideoState;
805 mCurrentVideoState = newVideoState;
806 mIsVideoMode = true;
807
808 // adjustVideoMode may be called if we are already in a 1-way video state. In this case
809 // we do not want to trigger auto-fullscreen mode.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700810 if (!isVideoCall(previousVideoState) && isVideoCall(newVideoState)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800811 maybeAutoEnterFullscreen(call);
812 }
813 }
814
815 private static boolean shouldShowVideoUiForCall(@Nullable DialerCall call) {
816 if (call == null) {
817 return false;
818 }
819
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700820 if (isVideoCall(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 return true;
822 }
823
824 if (isVideoUpgrade(call)) {
825 return true;
826 }
827
828 return false;
829 }
830
831 private void enableCamera(VideoCall videoCall, boolean isCameraRequired) {
832 LogUtil.v(
833 "VideoCallPresenter.enableCamera",
834 "videoCall: %s, enabling: %b",
835 videoCall,
836 isCameraRequired);
837 if (videoCall == null) {
838 LogUtil.i("VideoCallPresenter.enableCamera", "videoCall is null.");
839 return;
840 }
841
842 boolean hasCameraPermission = VideoUtils.hasCameraPermissionAndAllowedByUser(mContext);
843 if (!hasCameraPermission) {
844 videoCall.setCamera(null);
845 mPreviewSurfaceState = PreviewSurfaceState.NONE;
846 // TODO: Inform remote party that the video is off. This is similar to b/30256571.
847 } else if (isCameraRequired) {
848 InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
849 videoCall.setCamera(cameraManager.getActiveCameraId());
850 mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
851 videoCall.requestCameraCapabilities();
852 } else {
853 mPreviewSurfaceState = PreviewSurfaceState.NONE;
854 videoCall.setCamera(null);
855 }
856 }
857
858 /** Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */
859 private void exitVideoMode() {
860 LogUtil.i("VideoCallPresenter.exitVideoMode", "");
861
862 showVideoUi(
863 VideoProfile.STATE_AUDIO_ONLY,
864 DialerCall.State.ACTIVE,
Eric Erfanian90508232017-03-24 09:31:16 -0700865 SessionModificationState.NO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800866 false /* isRemotelyHeld */);
867 enableCamera(mVideoCall, false);
868 InCallPresenter.getInstance().setFullScreen(false);
869
870 mIsVideoMode = false;
871 }
872
873 /**
874 * Based on the current video state and call state, show or hide the incoming and outgoing video
875 * surfaces. The outgoing video surface is shown any time video is transmitting. The incoming
876 * video surface is shown whenever the video is un-paused and active.
877 *
878 * @param videoState The video state.
879 * @param callState The call state.
880 */
881 private void showVideoUi(
882 int videoState,
883 int callState,
884 @SessionModificationState int sessionModificationState,
885 boolean isRemotelyHeld) {
886 if (mVideoCallScreen == null) {
887 LogUtil.e("VideoCallPresenter.showVideoUi", "videoCallScreen is null returning");
888 return;
889 }
890 boolean showIncomingVideo = showIncomingVideo(videoState, callState);
891 boolean showOutgoingVideo = showOutgoingVideo(mContext, videoState, sessionModificationState);
892 LogUtil.i(
893 "VideoCallPresenter.showVideoUi",
894 "showIncoming: %b, showOutgoing: %b, isRemotelyHeld: %b",
895 showIncomingVideo,
896 showOutgoingVideo,
897 isRemotelyHeld);
898 updateRemoteVideoSurfaceDimensions();
899 mVideoCallScreen.showVideoViews(showOutgoingVideo, showIncomingVideo, isRemotelyHeld);
900
901 InCallPresenter.getInstance().enableScreenTimeout(VideoProfile.isAudioOnly(videoState));
902 updateFullscreenAndGreenScreenMode(callState, sessionModificationState);
903 }
904
905 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800906 * Handles peer video dimension changes.
907 *
908 * @param call The call which experienced a peer video dimension change.
909 * @param width The new peer video width .
910 * @param height The new peer video height.
911 */
912 @Override
913 public void onUpdatePeerDimensions(DialerCall call, int width, int height) {
914 LogUtil.i("VideoCallPresenter.onUpdatePeerDimensions", "width: %d, height: %d", width, height);
915 if (mVideoCallScreen == null) {
916 LogUtil.e("VideoCallPresenter.onUpdatePeerDimensions", "videoCallScreen is null");
917 return;
918 }
919 if (!call.equals(mPrimaryCall)) {
920 LogUtil.e(
921 "VideoCallPresenter.onUpdatePeerDimensions", "current call is not equal to primary");
922 return;
923 }
924
925 // Change size of display surface to match the peer aspect ratio
926 if (width > 0 && height > 0 && mVideoCallScreen != null) {
927 getRemoteVideoSurfaceTexture().setSourceVideoDimensions(new Point(width, height));
928 mVideoCallScreen.onRemoteVideoDimensionsChanged();
929 }
930 }
931
932 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800933 * Handles a change to the dimensions of the local camera. Receiving the camera capabilities
934 * triggers the creation of the video
935 *
936 * @param call The call which experienced the camera dimension change.
937 * @param width The new camera video width.
938 * @param height The new camera video height.
939 */
940 @Override
941 public void onCameraDimensionsChange(DialerCall call, int width, int height) {
942 LogUtil.i(
943 "VideoCallPresenter.onCameraDimensionsChange",
944 "call: %s, width: %d, height: %d",
945 call,
946 width,
947 height);
948 if (mVideoCallScreen == null) {
949 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "ui is null");
950 return;
951 }
952
953 if (!call.equals(mPrimaryCall)) {
954 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "not the primary call");
955 return;
956 }
957
958 mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
959 changePreviewDimensions(width, height);
960
961 // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
962 // If it not yet ready, it will be set when when creation completes.
963 Surface surface = getLocalVideoSurfaceTexture().getSavedSurface();
964 if (surface != null) {
965 mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
966 mVideoCall.setPreviewSurface(surface);
967 }
968 }
969
970 /**
971 * Changes the dimensions of the preview surface.
972 *
973 * @param width The new width.
974 * @param height The new height.
975 */
976 private void changePreviewDimensions(int width, int height) {
977 if (mVideoCallScreen == null) {
978 return;
979 }
980
981 // Resize the surface used to display the preview video
982 getLocalVideoSurfaceTexture().setSurfaceDimensions(new Point(width, height));
983 mVideoCallScreen.onLocalVideoDimensionsChanged();
984 }
985
986 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800987 * Handles changes to the device orientation.
988 *
989 * @param orientation The screen orientation of the device (one of: {@link
990 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
991 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
992 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
993 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
994 */
995 @Override
996 public void onDeviceOrientationChanged(int orientation) {
997 LogUtil.i(
998 "VideoCallPresenter.onDeviceOrientationChanged",
999 "orientation: %d -> %d",
1000 mDeviceOrientation,
1001 orientation);
1002 mDeviceOrientation = orientation;
1003
1004 if (mVideoCallScreen == null) {
1005 LogUtil.e("VideoCallPresenter.onDeviceOrientationChanged", "videoCallScreen is null");
1006 return;
1007 }
1008
1009 Point previewDimensions = getLocalVideoSurfaceTexture().getSurfaceDimensions();
1010 if (previewDimensions == null) {
1011 return;
1012 }
1013 LogUtil.v(
1014 "VideoCallPresenter.onDeviceOrientationChanged",
1015 "orientation: %d, size: %s",
1016 orientation,
1017 previewDimensions);
1018 changePreviewDimensions(previewDimensions.x, previewDimensions.y);
1019
1020 mVideoCallScreen.onLocalVideoOrientationChanged();
1021 }
1022
1023 /**
1024 * Exits fullscreen mode if the current call context has changed to a non-video call.
1025 *
1026 * @param call The call.
1027 */
1028 protected void maybeExitFullscreen(DialerCall call) {
1029 if (call == null) {
1030 return;
1031 }
1032
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001033 if (!isVideoCall(call) || call.getState() == DialerCall.State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001034 LogUtil.i("VideoCallPresenter.maybeExitFullscreen", "exiting fullscreen");
1035 InCallPresenter.getInstance().setFullScreen(false);
1036 }
1037 }
1038
1039 /**
1040 * Schedules auto-entering of fullscreen mode. Will not enter full screen mode if any of the
1041 * following conditions are met: 1. No call 2. DialerCall is not active 3. The current video state
1042 * is not bi-directional. 4. Already in fullscreen mode 5. In accessibility mode
1043 *
1044 * @param call The current call.
1045 */
1046 protected void maybeAutoEnterFullscreen(DialerCall call) {
1047 if (!mIsAutoFullscreenEnabled) {
1048 return;
1049 }
1050
1051 if (call == null
1052 || call.getState() != DialerCall.State.ACTIVE
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001053 || !isBidirectionalVideoCall(call)
Eric Erfanianccca3152017-02-22 16:32:36 -08001054 || InCallPresenter.getInstance().isFullscreen()
1055 || (mContext != null && AccessibilityUtil.isTouchExplorationEnabled(mContext))) {
1056 // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
1057 cancelAutoFullScreen();
1058 return;
1059 }
1060
1061 if (mAutoFullScreenPending) {
1062 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "already pending.");
1063 return;
1064 }
1065 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "scheduled");
1066 mAutoFullScreenPending = true;
1067 mHandler.removeCallbacks(mAutoFullscreenRunnable);
1068 mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis);
1069 }
1070
1071 /** Cancels pending auto fullscreen mode. */
1072 @Override
1073 public void cancelAutoFullScreen() {
1074 if (!mAutoFullScreenPending) {
1075 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "none pending.");
1076 return;
1077 }
1078 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "cancelling pending");
1079 mAutoFullScreenPending = false;
1080 mHandler.removeCallbacks(mAutoFullscreenRunnable);
1081 }
1082
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001083 @Override
1084 public boolean shouldShowCameraPermissionDialog() {
1085 if (mPrimaryCall == null) {
1086 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionDialog", "null call");
1087 return false;
1088 }
1089 if (mPrimaryCall.didShowCameraPermission()) {
1090 LogUtil.i(
1091 "VideoCallPresenter.shouldShowCameraPermissionDialog", "already shown for this call");
1092 return false;
1093 }
1094 if (!ConfigProviderBindings.get(mContext)
1095 .getBoolean("camera_permission_dialog_allowed", true)) {
1096 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionDialog", "disabled by config");
1097 return false;
1098 }
1099 return !VideoUtils.hasCameraPermission(mContext) || !VideoUtils.isCameraAllowedByUser(mContext);
1100 }
1101
1102 @Override
1103 public void onCameraPermissionDialogShown() {
1104 if (mPrimaryCall != null) {
1105 mPrimaryCall.setDidShowCameraPermission(true);
1106 }
1107 }
1108
Eric Erfanianccca3152017-02-22 16:32:36 -08001109 private void updateRemoteVideoSurfaceDimensions() {
1110 Activity activity = mVideoCallScreen.getVideoCallScreenFragment().getActivity();
1111 if (activity != null) {
1112 Point screenSize = new Point();
1113 activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
1114 getRemoteVideoSurfaceTexture().setSurfaceDimensions(screenSize);
1115 }
1116 }
1117
1118 private static boolean isVideoUpgrade(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001119 return call != null
1120 && (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
Eric Erfanianccca3152017-02-22 16:32:36 -08001121 }
1122
1123 private static boolean isVideoUpgrade(@SessionModificationState int state) {
1124 return VideoUtils.hasSentVideoUpgradeRequest(state)
1125 || VideoUtils.hasReceivedVideoUpgradeRequest(state);
1126 }
1127
1128 private class LocalDelegate implements VideoSurfaceDelegate {
1129 @Override
1130 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
1131 if (mVideoCallScreen == null) {
1132 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no UI");
1133 return;
1134 }
1135 if (mVideoCall == null) {
1136 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no video call");
1137 return;
1138 }
1139
1140 // If the preview surface has just been created and we have already received camera
1141 // capabilities, but not yet set the surface, we will set the surface now.
1142 if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
1143 mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
1144 mVideoCall.setPreviewSurface(videoCallSurface.getSavedSurface());
1145 } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
1146 enableCamera(mVideoCall, true);
1147 }
1148 }
1149
1150 @Override
1151 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
1152 if (mVideoCall == null) {
1153 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceReleased", "no video call");
1154 return;
1155 }
1156
1157 mVideoCall.setPreviewSurface(null);
1158 enableCamera(mVideoCall, false);
1159 }
1160
1161 @Override
1162 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {
1163 if (mVideoCall == null) {
1164 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", "no video call");
1165 return;
1166 }
1167
1168 boolean isChangingConfigurations = InCallPresenter.getInstance().isChangingConfigurations();
1169 if (!isChangingConfigurations) {
1170 enableCamera(mVideoCall, false);
1171 } else {
1172 LogUtil.i(
1173 "VideoCallPresenter.LocalDelegate.onSurfaceDestroyed",
1174 "activity is being destroyed due to configuration changes. Not closing the camera.");
1175 }
1176 }
1177
1178 @Override
1179 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1180 VideoCallPresenter.this.onSurfaceClick();
1181 }
1182 }
1183
1184 private class RemoteDelegate implements VideoSurfaceDelegate {
1185 @Override
1186 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
1187 if (mVideoCallScreen == null) {
1188 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no UI");
1189 return;
1190 }
1191 if (mVideoCall == null) {
1192 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no video call");
1193 return;
1194 }
1195 mVideoCall.setDisplaySurface(videoCallSurface.getSavedSurface());
1196 }
1197
1198 @Override
1199 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
1200 if (mVideoCall == null) {
1201 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceReleased", "no video call");
1202 return;
1203 }
1204 mVideoCall.setDisplaySurface(null);
1205 }
1206
1207 @Override
1208 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {}
1209
1210 @Override
1211 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1212 VideoCallPresenter.this.onSurfaceClick();
1213 }
1214 }
1215
1216 /** Defines the state of the preview surface negotiation with the telephony layer. */
1217 private static class PreviewSurfaceState {
1218
1219 /**
1220 * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet started.
1221 */
1222 private static final int NONE = 0;
1223
1224 /**
1225 * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet been
1226 * received.
1227 */
1228 private static final int CAMERA_SET = 1;
1229
1230 /**
1231 * The camera capabilties have been received from telephony, but the surface has not yet been
1232 * set on the {@link VideoCall}.
1233 */
1234 private static final int CAPABILITIES_RECEIVED = 2;
1235
1236 /** The surface has been set on the {@link VideoCall}. */
1237 private static final int SURFACE_SET = 3;
1238 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001239
1240 private static boolean isBidirectionalVideoCall(DialerCall call) {
1241 return CompatUtils.isVideoCompatible() && VideoProfile.isBidirectional(call.getVideoState());
1242 }
1243
1244 private static boolean isIncomingVideoCall(DialerCall call) {
1245 if (!isVideoCall(call)) {
1246 return false;
1247 }
1248 final int state = call.getState();
1249 return (state == DialerCall.State.INCOMING) || (state == DialerCall.State.CALL_WAITING);
1250 }
1251
1252 private static boolean isActiveVideoCall(DialerCall call) {
1253 return isVideoCall(call) && call.getState() == DialerCall.State.ACTIVE;
1254 }
1255
1256 private static boolean isOutgoingVideoCall(DialerCall call) {
1257 if (!isVideoCall(call)) {
1258 return false;
1259 }
1260 final int state = call.getState();
1261 return DialerCall.State.isDialing(state)
1262 || state == DialerCall.State.CONNECTING
1263 || state == DialerCall.State.SELECT_PHONE_ACCOUNT;
1264 }
1265
1266 private static boolean isAudioCall(DialerCall call) {
1267 if (!CompatUtils.isVideoCompatible()) {
1268 return true;
1269 }
1270
1271 return call != null && VideoProfile.isAudioOnly(call.getVideoState());
1272 }
1273
1274 private static boolean isVideoCall(@Nullable DialerCall call) {
1275 return call != null && call.isVideoCall();
1276 }
1277
1278 private static boolean isVideoCall(int videoState) {
1279 return CompatUtils.isVideoCompatible()
1280 && (VideoProfile.isTransmissionEnabled(videoState)
1281 || VideoProfile.isReceptionEnabled(videoState));
1282 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001283}