blob: a19d45f7a5079ca514b6d8a7a652ddd188225191 [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;
Eric Erfanianccca3152017-02-22 16:32:36 -080030import com.android.dialer.common.LogUtil;
31import com.android.dialer.compat.CompatUtils;
Eric Erfanian2ca43182017-08-31 06:57:16 -070032import com.android.dialer.configprovider.ConfigProviderBindings;
33import 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.DialerCall.State;
42import com.android.incallui.call.InCallVideoCallCallbackNotifier;
43import com.android.incallui.call.InCallVideoCallCallbackNotifier.SurfaceChangeListener;
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,
83 VideoCallScreenDelegate {
84
linyuh183cb712017-12-27 17:02:37 -080085 private static boolean isVideoMode = false;
Eric Erfanianccca3152017-02-22 16:32:36 -080086
linyuh183cb712017-12-27 17:02:37 -080087 private final Handler handler = new Handler();
88 private VideoCallScreen videoCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -080089
90 /** The current context. */
linyuh183cb712017-12-27 17:02:37 -080091 private Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -080092
Eric Erfanianccca3152017-02-22 16:32:36 -080093 /** The call the video surfaces are currently related to */
linyuh183cb712017-12-27 17:02:37 -080094 private DialerCall primaryCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080095 /**
96 * The {@link VideoCall} used to inform the video telephony layer of changes to the video
97 * surfaces.
98 */
linyuh183cb712017-12-27 17:02:37 -080099 private VideoCall videoCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800100 /** Determines if the current UI state represents a video call. */
linyuh183cb712017-12-27 17:02:37 -0800101 private int currentVideoState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800102 /** DialerCall's current state */
linyuh183cb712017-12-27 17:02:37 -0800103 private int currentCallState = DialerCall.State.INVALID;
Eric Erfanianccca3152017-02-22 16:32:36 -0800104 /** Determines the device orientation (portrait/lanscape). */
linyuh183cb712017-12-27 17:02:37 -0800105 private int deviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800106 /** Tracks the state of the preview surface negotiation with the telephony layer. */
linyuh183cb712017-12-27 17:02:37 -0800107 private int previewSurfaceState = PreviewSurfaceState.NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 /**
109 * Determines whether video calls should automatically enter full screen mode after {@link
linyuh183cb712017-12-27 17:02:37 -0800110 * #autoFullscreenTimeoutMillis} milliseconds.
Eric Erfanianccca3152017-02-22 16:32:36 -0800111 */
linyuh183cb712017-12-27 17:02:37 -0800112 private boolean isAutoFullscreenEnabled = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800113 /**
114 * Determines the number of milliseconds after which a video call will automatically enter
linyuh183cb712017-12-27 17:02:37 -0800115 * fullscreen mode. Requires {@link #isAutoFullscreenEnabled} to be {@code true}.
Eric Erfanianccca3152017-02-22 16:32:36 -0800116 */
linyuh183cb712017-12-27 17:02:37 -0800117 private int autoFullscreenTimeoutMillis = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800118 /**
119 * Determines if the countdown is currently running to automatically enter full screen video mode.
120 */
linyuh183cb712017-12-27 17:02:37 -0800121 private boolean autoFullScreenPending = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800122 /** Whether if the call is remotely held. */
linyuh183cb712017-12-27 17:02:37 -0800123 private boolean isRemotelyHeld = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800124 /**
125 * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto
126 * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit the
127 * dialpad).
128 */
linyuh183cb712017-12-27 17:02:37 -0800129 private Runnable autoFullscreenRunnable =
Eric Erfanianccca3152017-02-22 16:32:36 -0800130 new Runnable() {
131 @Override
132 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800133 if (autoFullScreenPending
Eric Erfanianccca3152017-02-22 16:32:36 -0800134 && !InCallPresenter.getInstance().isDialpadVisible()
linyuh183cb712017-12-27 17:02:37 -0800135 && isVideoMode) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800136
137 LogUtil.v("VideoCallPresenter.mAutoFullScreenRunnable", "entering fullscreen mode");
138 InCallPresenter.getInstance().setFullScreen(true);
linyuh183cb712017-12-27 17:02:37 -0800139 autoFullScreenPending = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800140 } else {
141 LogUtil.v(
142 "VideoCallPresenter.mAutoFullScreenRunnable",
143 "skipping scheduled fullscreen mode.");
144 }
145 }
146 };
147
148 private boolean isVideoCallScreenUiReady;
149
150 private static boolean isCameraRequired(int videoState, int sessionModificationState) {
151 return VideoProfile.isBidirectional(videoState)
152 || VideoProfile.isTransmissionEnabled(videoState)
153 || isVideoUpgrade(sessionModificationState);
154 }
155
156 /**
157 * Determines if the incoming video surface should be shown based on the current videoState and
Eric Erfaniand8046e52017-04-06 09:41:50 -0700158 * callState. The video surface is shown when incoming video is not paused, the call is active or
159 * dialing and video reception is enabled.
Eric Erfanianccca3152017-02-22 16:32:36 -0800160 *
161 * @param videoState The current video state.
162 * @param callState The current call state.
163 * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise.
164 */
165 public static boolean showIncomingVideo(int videoState, int callState) {
166 if (!CompatUtils.isVideoCompatible()) {
167 return false;
168 }
169
170 boolean isPaused = VideoProfile.isPaused(videoState);
171 boolean isCallActive = callState == DialerCall.State.ACTIVE;
wangqi385a5a12017-09-28 10:44:54 -0700172 // Show incoming Video for dialing calls to support early media
Eric Erfaniand8046e52017-04-06 09:41:50 -0700173 boolean isCallOutgoingPending =
174 DialerCall.State.isDialing(callState) || callState == DialerCall.State.CONNECTING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800175
Eric Erfaniand8046e52017-04-06 09:41:50 -0700176 return !isPaused
177 && (isCallActive || isCallOutgoingPending)
178 && VideoProfile.isReceptionEnabled(videoState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800179 }
180
181 /**
182 * Determines if the outgoing video surface should be shown based on the current videoState. The
183 * video surface is shown if video transmission is enabled.
184 *
185 * @return {@code true} if the the outgoing video surface should be shown, {@code false}
186 * otherwise.
187 */
188 public static boolean showOutgoingVideo(
189 Context context, int videoState, int sessionModificationState) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700190 if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(context)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800191 LogUtil.i("VideoCallPresenter.showOutgoingVideo", "Camera permission is disabled by user.");
192 return false;
193 }
194
195 if (!CompatUtils.isVideoCompatible()) {
196 return false;
197 }
198
199 return VideoProfile.isTransmissionEnabled(videoState)
200 || isVideoUpgrade(sessionModificationState);
201 }
202
203 private static void updateCameraSelection(DialerCall call) {
204 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + call);
205 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + toSimpleString(call));
206
207 final DialerCall activeCall = CallList.getInstance().getActiveCall();
208 int cameraDir;
209
210 // this function should never be called with null call object, however if it happens we
211 // should handle it gracefully.
212 if (call == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700213 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800214 LogUtil.e(
215 "VideoCallPresenter.updateCameraSelection",
216 "call is null. Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
217 }
218
219 // Clear camera direction if this is not a video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700220 else if (isAudioCall(call) && !isVideoUpgrade(call)) {
221 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
222 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800223 }
224
225 // If this is a waiting video call, default to active call's camera,
226 // since we don't want to change the current camera for waiting call
227 // without user's permission.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700228 else if (isVideoCall(activeCall) && isIncomingVideoCall(call)) {
229 cameraDir = activeCall.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800230 }
231
232 // Infer the camera direction from the video state and store it,
233 // if this is an outgoing video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700234 else if (isOutgoingVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800235 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700236 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800237 }
238
239 // Use the stored camera dir if this is an outgoing video call for which camera direction
240 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700241 else if (isOutgoingVideoCall(call)) {
242 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800243 }
244
245 // Infer the camera direction from the video state and store it,
246 // if this is an active video call and camera direction is not set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700247 else if (isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800248 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700249 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800250 }
251
252 // Use the stored camera dir if this is an active video call for which camera direction
253 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700254 else if (isActiveVideoCall(call)) {
255 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800256 }
257
258 // For all other cases infer the camera direction but don't store it in the call object.
259 else {
260 cameraDir = toCameraDirection(call.getVideoState());
261 }
262
263 LogUtil.i(
264 "VideoCallPresenter.updateCameraSelection",
265 "setting camera direction to %d, call: %s",
266 cameraDir,
267 call);
268 final InCallCameraManager cameraManager =
269 InCallPresenter.getInstance().getInCallCameraManager();
270 cameraManager.setUseFrontFacingCamera(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700271 cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800272 }
273
274 private static int toCameraDirection(int videoState) {
275 return VideoProfile.isTransmissionEnabled(videoState)
276 && !VideoProfile.isBidirectional(videoState)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700277 ? CameraDirection.CAMERA_DIRECTION_BACK_FACING
278 : CameraDirection.CAMERA_DIRECTION_FRONT_FACING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800279 }
280
281 private static boolean isCameraDirectionSet(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700282 return isVideoCall(call) && call.getCameraDir() != CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800283 }
284
285 private static String toSimpleString(DialerCall call) {
286 return call == null ? null : call.toSimpleString();
287 }
288
289 /**
290 * Initializes the presenter.
291 *
292 * @param context The current context.
293 */
294 @Override
295 public void initVideoCallScreenDelegate(Context context, VideoCallScreen videoCallScreen) {
linyuh183cb712017-12-27 17:02:37 -0800296 this.context = context;
297 this.videoCallScreen = videoCallScreen;
298 isAutoFullscreenEnabled =
299 this.context.getResources().getBoolean(R.bool.video_call_auto_fullscreen);
300 autoFullscreenTimeoutMillis =
301 this.context.getResources().getInteger(R.integer.video_call_auto_fullscreen_timeout);
Eric Erfanianccca3152017-02-22 16:32:36 -0800302 }
303
304 /** Called when the user interface is ready to be used. */
305 @Override
306 public void onVideoCallScreenUiReady() {
307 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiReady", "");
308 Assert.checkState(!isVideoCallScreenUiReady);
309
310 // Do not register any listeners if video calling is not compatible to safeguard against
311 // any accidental calls of video calling code.
312 if (!CompatUtils.isVideoCompatible()) {
313 return;
314 }
315
linyuh183cb712017-12-27 17:02:37 -0800316 deviceOrientation = InCallOrientationEventListener.getCurrentOrientation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800317
318 // Register for call state changes last
319 InCallPresenter.getInstance().addListener(this);
320 InCallPresenter.getInstance().addDetailsListener(this);
321 InCallPresenter.getInstance().addIncomingCallListener(this);
322 InCallPresenter.getInstance().addOrientationListener(this);
323 // To get updates of video call details changes
324 InCallPresenter.getInstance().addInCallEventListener(this);
325 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate());
326 InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate());
327
328 // Register for surface and video events from {@link InCallVideoCallListener}s.
329 InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
linyuh183cb712017-12-27 17:02:37 -0800330 currentVideoState = VideoProfile.STATE_AUDIO_ONLY;
331 currentCallState = DialerCall.State.INVALID;
Eric Erfanianccca3152017-02-22 16:32:36 -0800332
333 InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState();
334 onStateChange(inCallState, inCallState, CallList.getInstance());
335 isVideoCallScreenUiReady = true;
336 }
337
338 /** Called when the user interface is no longer ready to be used. */
339 @Override
340 public void onVideoCallScreenUiUnready() {
341 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiUnready", "");
342 Assert.checkState(isVideoCallScreenUiReady);
343
344 if (!CompatUtils.isVideoCompatible()) {
345 return;
346 }
347
348 cancelAutoFullScreen();
349
350 InCallPresenter.getInstance().removeListener(this);
351 InCallPresenter.getInstance().removeDetailsListener(this);
352 InCallPresenter.getInstance().removeIncomingCallListener(this);
353 InCallPresenter.getInstance().removeOrientationListener(this);
354 InCallPresenter.getInstance().removeInCallEventListener(this);
355 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null);
356
357 InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800358
359 // Ensure that the call's camera direction is updated (most likely to UNKNOWN). Normally this
360 // happens after any call state changes but we're unregistering from InCallPresenter above so
Eric Erfanian938468d2017-10-24 14:05:52 -0700361 // we won't get any more call state changes. See a bug.
linyuh183cb712017-12-27 17:02:37 -0800362 if (primaryCall != null) {
363 updateCameraSelection(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800364 }
365
366 isVideoCallScreenUiReady = false;
367 }
368
369 /**
370 * Handles clicks on the video surfaces. If not currently in fullscreen mode, will set fullscreen.
371 */
372 private void onSurfaceClick() {
373 LogUtil.i("VideoCallPresenter.onSurfaceClick", "");
374 cancelAutoFullScreen();
375 if (!InCallPresenter.getInstance().isFullscreen()) {
376 InCallPresenter.getInstance().setFullScreen(true);
377 } else {
378 InCallPresenter.getInstance().setFullScreen(false);
linyuh183cb712017-12-27 17:02:37 -0800379 maybeAutoEnterFullscreen(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800380 // If Activity is not multiwindow, fullscreen will be driven by SystemUI visibility changes
381 // instead. See #onSystemUiVisibilityChange(boolean)
382
383 // TODO (keyboardr): onSystemUiVisibilityChange isn't being called the first time
384 // visibility changes after orientation change, so this is currently always done as a backup.
385 }
386 }
387
388 @Override
389 public void onSystemUiVisibilityChange(boolean visible) {
390 // If the SystemUI has changed to be visible, take us out of fullscreen mode
391 LogUtil.i("VideoCallPresenter.onSystemUiVisibilityChange", "visible: " + visible);
392 if (visible) {
393 InCallPresenter.getInstance().setFullScreen(false);
linyuh183cb712017-12-27 17:02:37 -0800394 maybeAutoEnterFullscreen(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800395 }
396 }
397
398 @Override
399 public VideoSurfaceTexture getLocalVideoSurfaceTexture() {
400 return InCallPresenter.getInstance().getLocalVideoSurfaceTexture();
401 }
402
403 @Override
404 public VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
405 return InCallPresenter.getInstance().getRemoteVideoSurfaceTexture();
406 }
407
408 @Override
Eric Erfanian90508232017-03-24 09:31:16 -0700409 public void setSurfaceViews(SurfaceView preview, SurfaceView remote) {
410 throw Assert.createUnsupportedOperationFailException();
411 }
412
413 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -0800414 public int getDeviceOrientation() {
linyuh183cb712017-12-27 17:02:37 -0800415 return deviceOrientation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800416 }
417
418 /**
419 * This should only be called when user approved the camera permission, which is local action and
420 * does NOT change any call states.
421 */
422 @Override
423 public void onCameraPermissionGranted() {
424 LogUtil.i("VideoCallPresenter.onCameraPermissionGranted", "");
linyuh183cb712017-12-27 17:02:37 -0800425 PermissionsUtil.setCameraPrivacyToastShown(context);
426 enableCamera(primaryCall, isCameraRequired());
Eric Erfanianccca3152017-02-22 16:32:36 -0800427 showVideoUi(
linyuh183cb712017-12-27 17:02:37 -0800428 primaryCall.getVideoState(),
429 primaryCall.getState(),
430 primaryCall.getVideoTech().getSessionModificationState(),
431 primaryCall.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800432 InCallPresenter.getInstance().getInCallCameraManager().onCameraPermissionGranted();
433 }
434
435 /**
436 * Called when the user interacts with the UI. If a fullscreen timer is pending then we start the
437 * timer from scratch to avoid having the UI disappear while the user is interacting with it.
438 */
439 @Override
440 public void resetAutoFullscreenTimer() {
linyuh183cb712017-12-27 17:02:37 -0800441 if (autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800442 LogUtil.i("VideoCallPresenter.resetAutoFullscreenTimer", "resetting");
linyuh183cb712017-12-27 17:02:37 -0800443 handler.removeCallbacks(autoFullscreenRunnable);
444 handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis);
Eric Erfanianccca3152017-02-22 16:32:36 -0800445 }
446 }
447
448 /**
449 * Handles incoming calls.
450 *
451 * @param oldState The old in call state.
452 * @param newState The new in call state.
453 * @param call The call.
454 */
455 @Override
456 public void onIncomingCall(
457 InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, DialerCall call) {
wangqi385a5a12017-09-28 10:44:54 -0700458 // If video call screen ui is already destroyed, this shouldn't be called. But the UI may be
459 // updated synchronized by {@link CallCardPresenter#onIncomingCall} before this is called, this
460 // could still be called. Thus just do nothing in this case.
461 if (!isVideoCallScreenUiReady) {
462 LogUtil.i("VideoCallPresenter.onIncomingCall", "UI is not ready");
463 return;
464 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800465 // same logic should happen as with onStateChange()
466 onStateChange(oldState, newState, CallList.getInstance());
467 }
468
469 /**
470 * Handles state changes (including incoming calls)
471 *
472 * @param newState The in call state.
473 * @param callList The call list.
474 */
475 @Override
476 public void onStateChange(
477 InCallPresenter.InCallState oldState,
478 InCallPresenter.InCallState newState,
479 CallList callList) {
480 LogUtil.v(
481 "VideoCallPresenter.onStateChange",
482 "oldState: %s, newState: %s, isVideoMode: %b",
483 oldState,
484 newState,
485 isVideoMode());
486
487 if (newState == InCallPresenter.InCallState.NO_CALLS) {
488 if (isVideoMode()) {
489 exitVideoMode();
490 }
491
492 InCallPresenter.getInstance().cleanupSurfaces();
493 }
494
495 // Determine the primary active call).
496 DialerCall primary = null;
497
498 // Determine the call which is the focus of the user's attention. In the case of an
499 // incoming call waiting call, the primary call is still the active video call, however
500 // the determination of whether we should be in fullscreen mode is based on the type of the
501 // incoming call, not the active video call.
502 DialerCall currentCall = null;
503
504 if (newState == InCallPresenter.InCallState.INCOMING) {
505 // We don't want to replace active video call (primary call)
506 // with a waiting call, since user may choose to ignore/decline the waiting call and
507 // this should have no impact on current active video call, that is, we should not
508 // change the camera or UI unless the waiting VT call becomes active.
509 primary = callList.getActiveCall();
510 currentCall = callList.getIncomingCall();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700511 if (!isActiveVideoCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800512 primary = callList.getIncomingCall();
513 }
514 } else if (newState == InCallPresenter.InCallState.OUTGOING) {
515 currentCall = primary = callList.getOutgoingCall();
516 } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
517 currentCall = primary = callList.getPendingOutgoingCall();
518 } else if (newState == InCallPresenter.InCallState.INCALL) {
519 currentCall = primary = callList.getActiveCall();
520 }
521
linyuh183cb712017-12-27 17:02:37 -0800522 final boolean primaryChanged = !Objects.equals(primaryCall, primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800523 LogUtil.i(
524 "VideoCallPresenter.onStateChange",
525 "primaryChanged: %b, primary: %s, mPrimaryCall: %s",
526 primaryChanged,
527 primary,
linyuh183cb712017-12-27 17:02:37 -0800528 primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800529 if (primaryChanged) {
530 onPrimaryCallChanged(primary);
linyuh183cb712017-12-27 17:02:37 -0800531 } else if (primaryCall != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800532 updateVideoCall(primary);
533 }
534 updateCallCache(primary);
535
536 // If the call context changed, potentially exit fullscreen or schedule auto enter of
537 // fullscreen mode.
538 // If the current call context is no longer a video call, exit fullscreen mode.
539 maybeExitFullscreen(currentCall);
540 // Schedule auto-enter of fullscreen mode if the current call context is a video call
541 maybeAutoEnterFullscreen(currentCall);
542 }
543
544 /**
545 * Handles a change to the fullscreen mode of the app.
546 *
547 * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise.
548 */
549 @Override
550 public void onFullscreenModeChanged(boolean isFullscreenMode) {
551 cancelAutoFullScreen();
linyuh183cb712017-12-27 17:02:37 -0800552 if (primaryCall != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800553 updateFullscreenAndGreenScreenMode(
linyuh183cb712017-12-27 17:02:37 -0800554 primaryCall.getState(), primaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800555 } else {
Eric Erfanian90508232017-03-24 09:31:16 -0700556 updateFullscreenAndGreenScreenMode(State.INVALID, SessionModificationState.NO_REQUEST);
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 }
558 }
559
560 private void checkForVideoStateChange(DialerCall call) {
561 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
linyuh183cb712017-12-27 17:02:37 -0800562 final boolean hasVideoStateChanged = currentVideoState != call.getVideoState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800563
564 LogUtil.v(
565 "VideoCallPresenter.checkForVideoStateChange",
566 "shouldShowVideoUi: %b, hasVideoStateChanged: %b, isVideoMode: %b, previousVideoState: %s,"
567 + " newVideoState: %s",
568 shouldShowVideoUi,
569 hasVideoStateChanged,
570 isVideoMode(),
linyuh183cb712017-12-27 17:02:37 -0800571 VideoProfile.videoStateToString(currentVideoState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800572 VideoProfile.videoStateToString(call.getVideoState()));
573 if (!hasVideoStateChanged) {
574 return;
575 }
576
577 updateCameraSelection(call);
578
579 if (shouldShowVideoUi) {
580 adjustVideoMode(call);
581 } else if (isVideoMode()) {
582 exitVideoMode();
583 }
584 }
585
586 private void checkForCallStateChange(DialerCall call) {
587 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
588 final boolean hasCallStateChanged =
linyuh183cb712017-12-27 17:02:37 -0800589 currentCallState != call.getState() || isRemotelyHeld != call.isRemotelyHeld();
590 isRemotelyHeld = call.isRemotelyHeld();
Eric Erfanianccca3152017-02-22 16:32:36 -0800591
592 LogUtil.v(
593 "VideoCallPresenter.checkForCallStateChange",
594 "shouldShowVideoUi: %b, hasCallStateChanged: %b, isVideoMode: %b",
595 shouldShowVideoUi,
596 hasCallStateChanged,
597 isVideoMode());
598
599 if (!hasCallStateChanged) {
600 return;
601 }
602
603 if (shouldShowVideoUi) {
604 final InCallCameraManager cameraManager =
605 InCallPresenter.getInstance().getInCallCameraManager();
606
607 String prevCameraId = cameraManager.getActiveCameraId();
608 updateCameraSelection(call);
609 String newCameraId = cameraManager.getActiveCameraId();
610
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700611 if (!Objects.equals(prevCameraId, newCameraId) && isActiveVideoCall(call)) {
roldenburgca475472017-10-25 13:00:42 -0700612 enableCamera(call, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800613 }
614 }
615
616 // Make sure we hide or show the video UI if needed.
617 showVideoUi(
618 call.getVideoState(),
619 call.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700620 call.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800621 call.isRemotelyHeld());
622 }
623
624 private void onPrimaryCallChanged(DialerCall newPrimaryCall) {
625 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(newPrimaryCall);
626 final boolean isVideoMode = isVideoMode();
627
628 LogUtil.v(
629 "VideoCallPresenter.onPrimaryCallChanged",
630 "shouldShowVideoUi: %b, isVideoMode: %b",
631 shouldShowVideoUi,
632 isVideoMode);
633
634 if (!shouldShowVideoUi && isVideoMode) {
635 // Terminate video mode if new primary call is not a video call
636 // and we are currently in video mode.
637 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "exiting video mode...");
638 exitVideoMode();
639 } else if (shouldShowVideoUi) {
640 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "entering video mode...");
641
642 updateCameraSelection(newPrimaryCall);
643 adjustVideoMode(newPrimaryCall);
644 }
645 checkForOrientationAllowedChange(newPrimaryCall);
646 }
647
648 private boolean isVideoMode() {
linyuh183cb712017-12-27 17:02:37 -0800649 return isVideoMode;
Eric Erfanianccca3152017-02-22 16:32:36 -0800650 }
651
652 private void updateCallCache(DialerCall call) {
653 if (call == null) {
linyuh183cb712017-12-27 17:02:37 -0800654 currentVideoState = VideoProfile.STATE_AUDIO_ONLY;
655 currentCallState = DialerCall.State.INVALID;
656 videoCall = null;
657 primaryCall = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800658 } else {
linyuh183cb712017-12-27 17:02:37 -0800659 currentVideoState = call.getVideoState();
660 videoCall = call.getVideoCall();
661 currentCallState = call.getState();
662 primaryCall = call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800663 }
664 }
665
666 /**
667 * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in
668 * changes to the video state.
669 *
670 * @param call The call for which the details changed.
671 * @param details The new call details.
672 */
673 @Override
674 public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) {
675 LogUtil.v(
676 "VideoCallPresenter.onDetailsChanged",
677 "call: %s, details: %s, mPrimaryCall: %s",
678 call,
679 details,
linyuh183cb712017-12-27 17:02:37 -0800680 primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800681 if (call == null) {
682 return;
683 }
684 // If the details change is not for the currently active call no update is required.
linyuh183cb712017-12-27 17:02:37 -0800685 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800686 LogUtil.v("VideoCallPresenter.onDetailsChanged", "details not for current active call");
687 return;
688 }
689
690 updateVideoCall(call);
691
692 updateCallCache(call);
693 }
694
695 private void updateVideoCall(DialerCall call) {
696 checkForVideoCallChange(call);
697 checkForVideoStateChange(call);
698 checkForCallStateChange(call);
699 checkForOrientationAllowedChange(call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700700 updateFullscreenAndGreenScreenMode(
701 call.getState(), call.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800702 }
703
704 private void checkForOrientationAllowedChange(@Nullable DialerCall call) {
705 InCallPresenter.getInstance()
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700706 .setInCallAllowsOrientationChange(isVideoCall(call) || isVideoUpgrade(call));
Eric Erfanianccca3152017-02-22 16:32:36 -0800707 }
708
709 private void updateFullscreenAndGreenScreenMode(
710 int callState, @SessionModificationState int sessionModificationState) {
linyuh183cb712017-12-27 17:02:37 -0800711 if (videoCallScreen != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800712 boolean shouldShowFullscreen = InCallPresenter.getInstance().isFullscreen();
713 boolean shouldShowGreenScreen =
714 callState == State.DIALING
715 || callState == State.CONNECTING
716 || callState == State.INCOMING
717 || isVideoUpgrade(sessionModificationState);
linyuh183cb712017-12-27 17:02:37 -0800718 videoCallScreen.updateFullscreenAndGreenScreenMode(
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 shouldShowFullscreen, shouldShowGreenScreen);
720 }
721 }
722
723 /** Checks for a change to the video call and changes it if required. */
724 private void checkForVideoCallChange(DialerCall call) {
725 final VideoCall videoCall = call.getVideoCall();
726 LogUtil.v(
727 "VideoCallPresenter.checkForVideoCallChange",
728 "videoCall: %s, mVideoCall: %s",
729 videoCall,
linyuh183cb712017-12-27 17:02:37 -0800730 this.videoCall);
731 if (!Objects.equals(videoCall, this.videoCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800732 changeVideoCall(call);
733 }
734 }
735
736 /**
737 * Handles a change to the video call. Sets the surfaces on the previous call to null and sets the
738 * surfaces on the new video call accordingly.
739 *
740 * @param call The new video call.
741 */
742 private void changeVideoCall(DialerCall call) {
743 final VideoCall videoCall = call == null ? null : call.getVideoCall();
744 LogUtil.i(
745 "VideoCallPresenter.changeVideoCall",
746 "videoCall: %s, mVideoCall: %s",
747 videoCall,
linyuh183cb712017-12-27 17:02:37 -0800748 this.videoCall);
749 final boolean hasChanged = this.videoCall == null && videoCall != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800750
linyuh183cb712017-12-27 17:02:37 -0800751 this.videoCall = videoCall;
752 if (this.videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800753 LogUtil.v("VideoCallPresenter.changeVideoCall", "video call or primary call is null. Return");
754 return;
755 }
756
757 if (shouldShowVideoUiForCall(call) && hasChanged) {
758 adjustVideoMode(call);
759 }
760 }
761
762 private boolean isCameraRequired() {
linyuh183cb712017-12-27 17:02:37 -0800763 return primaryCall != null
Eric Erfanianccca3152017-02-22 16:32:36 -0800764 && isCameraRequired(
linyuh183cb712017-12-27 17:02:37 -0800765 primaryCall.getVideoState(), primaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800766 }
767
768 /**
769 * Adjusts the current video mode by setting up the preview and display surfaces as necessary.
770 * Expected to be called whenever the video state associated with a call changes (e.g. a user
Eric Erfanian2ca43182017-08-31 06:57:16 -0700771 * turns their camera on or off) to ensure the correct surfaces are shown/hidden. TODO(vt): Need
Eric Erfanianccca3152017-02-22 16:32:36 -0800772 * to adjust size and orientation of preview surface here.
773 */
774 private void adjustVideoMode(DialerCall call) {
775 VideoCall videoCall = call.getVideoCall();
776 int newVideoState = call.getVideoState();
777
778 LogUtil.i(
779 "VideoCallPresenter.adjustVideoMode",
780 "videoCall: %s, videoState: %d",
781 videoCall,
782 newVideoState);
linyuh183cb712017-12-27 17:02:37 -0800783 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800784 LogUtil.e("VideoCallPresenter.adjustVideoMode", "error VideoCallScreen is null so returning");
785 return;
786 }
787
788 showVideoUi(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700789 newVideoState,
790 call.getState(),
791 call.getVideoTech().getSessionModificationState(),
792 call.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800793
794 // Communicate the current camera to telephony and make a request for the camera
795 // capabilities.
796 if (videoCall != null) {
797 Surface surface = getRemoteVideoSurfaceTexture().getSavedSurface();
798 if (surface != null) {
799 LogUtil.v(
800 "VideoCallPresenter.adjustVideoMode", "calling setDisplaySurface with: " + surface);
801 videoCall.setDisplaySurface(surface);
802 }
803
804 Assert.checkState(
linyuh183cb712017-12-27 17:02:37 -0800805 deviceOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN);
806 videoCall.setDeviceOrientation(deviceOrientation);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700807 enableCamera(
roldenburgca475472017-10-25 13:00:42 -0700808 call, isCameraRequired(newVideoState, call.getVideoTech().getSessionModificationState()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800809 }
linyuh183cb712017-12-27 17:02:37 -0800810 int previousVideoState = currentVideoState;
811 currentVideoState = newVideoState;
812 isVideoMode = true;
Eric Erfanianccca3152017-02-22 16:32:36 -0800813
814 // adjustVideoMode may be called if we are already in a 1-way video state. In this case
815 // we do not want to trigger auto-fullscreen mode.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700816 if (!isVideoCall(previousVideoState) && isVideoCall(newVideoState)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800817 maybeAutoEnterFullscreen(call);
818 }
819 }
820
821 private static boolean shouldShowVideoUiForCall(@Nullable DialerCall call) {
822 if (call == null) {
823 return false;
824 }
825
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700826 if (isVideoCall(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800827 return true;
828 }
829
830 if (isVideoUpgrade(call)) {
831 return true;
832 }
833
834 return false;
835 }
836
roldenburgca475472017-10-25 13:00:42 -0700837 private void enableCamera(DialerCall call, boolean isCameraRequired) {
838 LogUtil.v("VideoCallPresenter.enableCamera", "call: %s, enabling: %b", call, isCameraRequired);
839 if (call == null) {
840 LogUtil.i("VideoCallPresenter.enableCamera", "call is null");
Eric Erfanianccca3152017-02-22 16:32:36 -0800841 return;
842 }
843
linyuh183cb712017-12-27 17:02:37 -0800844 boolean hasCameraPermission = VideoUtils.hasCameraPermissionAndShownPrivacyToast(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800845 if (!hasCameraPermission) {
roldenburgca475472017-10-25 13:00:42 -0700846 call.getVideoTech().setCamera(null);
linyuh183cb712017-12-27 17:02:37 -0800847 previewSurfaceState = PreviewSurfaceState.NONE;
Eric Erfanian938468d2017-10-24 14:05:52 -0700848 // TODO(wangqi): Inform remote party that the video is off. This is similar to a bug.
Eric Erfanianccca3152017-02-22 16:32:36 -0800849 } else if (isCameraRequired) {
850 InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
roldenburgca475472017-10-25 13:00:42 -0700851 call.getVideoTech().setCamera(cameraManager.getActiveCameraId());
linyuh183cb712017-12-27 17:02:37 -0800852 previewSurfaceState = PreviewSurfaceState.CAMERA_SET;
Eric Erfanianccca3152017-02-22 16:32:36 -0800853 } else {
linyuh183cb712017-12-27 17:02:37 -0800854 previewSurfaceState = PreviewSurfaceState.NONE;
roldenburgca475472017-10-25 13:00:42 -0700855 call.getVideoTech().setCamera(null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800856 }
857 }
858
859 /** Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */
860 private void exitVideoMode() {
861 LogUtil.i("VideoCallPresenter.exitVideoMode", "");
862
863 showVideoUi(
864 VideoProfile.STATE_AUDIO_ONLY,
865 DialerCall.State.ACTIVE,
Eric Erfanian90508232017-03-24 09:31:16 -0700866 SessionModificationState.NO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800867 false /* isRemotelyHeld */);
linyuh183cb712017-12-27 17:02:37 -0800868 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800869 InCallPresenter.getInstance().setFullScreen(false);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700870 InCallPresenter.getInstance().enableScreenTimeout(false);
linyuh183cb712017-12-27 17:02:37 -0800871 isVideoMode = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800872 }
873
874 /**
875 * Based on the current video state and call state, show or hide the incoming and outgoing video
876 * surfaces. The outgoing video surface is shown any time video is transmitting. The incoming
877 * video surface is shown whenever the video is un-paused and active.
878 *
879 * @param videoState The video state.
880 * @param callState The call state.
881 */
882 private void showVideoUi(
883 int videoState,
884 int callState,
885 @SessionModificationState int sessionModificationState,
886 boolean isRemotelyHeld) {
linyuh183cb712017-12-27 17:02:37 -0800887 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800888 LogUtil.e("VideoCallPresenter.showVideoUi", "videoCallScreen is null returning");
889 return;
890 }
891 boolean showIncomingVideo = showIncomingVideo(videoState, callState);
linyuh183cb712017-12-27 17:02:37 -0800892 boolean showOutgoingVideo = showOutgoingVideo(context, videoState, sessionModificationState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800893 LogUtil.i(
894 "VideoCallPresenter.showVideoUi",
895 "showIncoming: %b, showOutgoing: %b, isRemotelyHeld: %b",
896 showIncomingVideo,
897 showOutgoingVideo,
898 isRemotelyHeld);
899 updateRemoteVideoSurfaceDimensions();
linyuh183cb712017-12-27 17:02:37 -0800900 videoCallScreen.showVideoViews(showOutgoingVideo, showIncomingVideo, isRemotelyHeld);
Eric Erfanianccca3152017-02-22 16:32:36 -0800901
902 InCallPresenter.getInstance().enableScreenTimeout(VideoProfile.isAudioOnly(videoState));
903 updateFullscreenAndGreenScreenMode(callState, sessionModificationState);
904 }
905
906 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800907 * Handles peer video dimension changes.
908 *
909 * @param call The call which experienced a peer video dimension change.
910 * @param width The new peer video width .
911 * @param height The new peer video height.
912 */
913 @Override
914 public void onUpdatePeerDimensions(DialerCall call, int width, int height) {
915 LogUtil.i("VideoCallPresenter.onUpdatePeerDimensions", "width: %d, height: %d", width, height);
linyuh183cb712017-12-27 17:02:37 -0800916 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800917 LogUtil.e("VideoCallPresenter.onUpdatePeerDimensions", "videoCallScreen is null");
918 return;
919 }
linyuh183cb712017-12-27 17:02:37 -0800920 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800921 LogUtil.e(
922 "VideoCallPresenter.onUpdatePeerDimensions", "current call is not equal to primary");
923 return;
924 }
925
926 // Change size of display surface to match the peer aspect ratio
linyuh183cb712017-12-27 17:02:37 -0800927 if (width > 0 && height > 0 && videoCallScreen != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800928 getRemoteVideoSurfaceTexture().setSourceVideoDimensions(new Point(width, height));
linyuh183cb712017-12-27 17:02:37 -0800929 videoCallScreen.onRemoteVideoDimensionsChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800930 }
931 }
932
933 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800934 * Handles a change to the dimensions of the local camera. Receiving the camera capabilities
935 * triggers the creation of the video
936 *
937 * @param call The call which experienced the camera dimension change.
938 * @param width The new camera video width.
939 * @param height The new camera video height.
940 */
941 @Override
942 public void onCameraDimensionsChange(DialerCall call, int width, int height) {
943 LogUtil.i(
944 "VideoCallPresenter.onCameraDimensionsChange",
945 "call: %s, width: %d, height: %d",
946 call,
947 width,
948 height);
linyuh183cb712017-12-27 17:02:37 -0800949 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800950 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "ui is null");
951 return;
952 }
953
linyuh183cb712017-12-27 17:02:37 -0800954 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800955 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "not the primary call");
956 return;
957 }
958
linyuh183cb712017-12-27 17:02:37 -0800959 previewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
Eric Erfanianccca3152017-02-22 16:32:36 -0800960 changePreviewDimensions(width, height);
961
962 // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
963 // If it not yet ready, it will be set when when creation completes.
964 Surface surface = getLocalVideoSurfaceTexture().getSavedSurface();
965 if (surface != null) {
linyuh183cb712017-12-27 17:02:37 -0800966 previewSurfaceState = PreviewSurfaceState.SURFACE_SET;
967 videoCall.setPreviewSurface(surface);
Eric Erfanianccca3152017-02-22 16:32:36 -0800968 }
969 }
970
971 /**
972 * Changes the dimensions of the preview surface.
973 *
974 * @param width The new width.
975 * @param height The new height.
976 */
977 private void changePreviewDimensions(int width, int height) {
linyuh183cb712017-12-27 17:02:37 -0800978 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800979 return;
980 }
981
982 // Resize the surface used to display the preview video
983 getLocalVideoSurfaceTexture().setSurfaceDimensions(new Point(width, height));
linyuh183cb712017-12-27 17:02:37 -0800984 videoCallScreen.onLocalVideoDimensionsChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 }
986
987 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800988 * Handles changes to the device orientation.
989 *
990 * @param orientation The screen orientation of the device (one of: {@link
991 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
992 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
993 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
994 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
995 */
996 @Override
997 public void onDeviceOrientationChanged(int orientation) {
998 LogUtil.i(
999 "VideoCallPresenter.onDeviceOrientationChanged",
1000 "orientation: %d -> %d",
linyuh183cb712017-12-27 17:02:37 -08001001 deviceOrientation,
Eric Erfanianccca3152017-02-22 16:32:36 -08001002 orientation);
linyuh183cb712017-12-27 17:02:37 -08001003 deviceOrientation = orientation;
Eric Erfanianccca3152017-02-22 16:32:36 -08001004
linyuh183cb712017-12-27 17:02:37 -08001005 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001006 LogUtil.e("VideoCallPresenter.onDeviceOrientationChanged", "videoCallScreen is null");
1007 return;
1008 }
1009
1010 Point previewDimensions = getLocalVideoSurfaceTexture().getSurfaceDimensions();
1011 if (previewDimensions == null) {
1012 return;
1013 }
1014 LogUtil.v(
1015 "VideoCallPresenter.onDeviceOrientationChanged",
1016 "orientation: %d, size: %s",
1017 orientation,
1018 previewDimensions);
1019 changePreviewDimensions(previewDimensions.x, previewDimensions.y);
1020
linyuh183cb712017-12-27 17:02:37 -08001021 videoCallScreen.onLocalVideoOrientationChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001022 }
1023
1024 /**
1025 * Exits fullscreen mode if the current call context has changed to a non-video call.
1026 *
1027 * @param call The call.
1028 */
1029 protected void maybeExitFullscreen(DialerCall call) {
1030 if (call == null) {
1031 return;
1032 }
1033
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001034 if (!isVideoCall(call) || call.getState() == DialerCall.State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001035 LogUtil.i("VideoCallPresenter.maybeExitFullscreen", "exiting fullscreen");
1036 InCallPresenter.getInstance().setFullScreen(false);
1037 }
1038 }
1039
1040 /**
1041 * Schedules auto-entering of fullscreen mode. Will not enter full screen mode if any of the
1042 * following conditions are met: 1. No call 2. DialerCall is not active 3. The current video state
1043 * is not bi-directional. 4. Already in fullscreen mode 5. In accessibility mode
1044 *
1045 * @param call The current call.
1046 */
1047 protected void maybeAutoEnterFullscreen(DialerCall call) {
linyuh183cb712017-12-27 17:02:37 -08001048 if (!isAutoFullscreenEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001049 return;
1050 }
1051
1052 if (call == null
1053 || call.getState() != DialerCall.State.ACTIVE
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001054 || !isBidirectionalVideoCall(call)
Eric Erfanianccca3152017-02-22 16:32:36 -08001055 || InCallPresenter.getInstance().isFullscreen()
linyuh183cb712017-12-27 17:02:37 -08001056 || (context != null && AccessibilityUtil.isTouchExplorationEnabled(context))) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001057 // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
1058 cancelAutoFullScreen();
1059 return;
1060 }
1061
linyuh183cb712017-12-27 17:02:37 -08001062 if (autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001063 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "already pending.");
1064 return;
1065 }
1066 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "scheduled");
linyuh183cb712017-12-27 17:02:37 -08001067 autoFullScreenPending = true;
1068 handler.removeCallbacks(autoFullscreenRunnable);
1069 handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis);
Eric Erfanianccca3152017-02-22 16:32:36 -08001070 }
1071
1072 /** Cancels pending auto fullscreen mode. */
1073 @Override
1074 public void cancelAutoFullScreen() {
linyuh183cb712017-12-27 17:02:37 -08001075 if (!autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001076 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "none pending.");
1077 return;
1078 }
1079 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "cancelling pending");
linyuh183cb712017-12-27 17:02:37 -08001080 autoFullScreenPending = false;
1081 handler.removeCallbacks(autoFullscreenRunnable);
Eric Erfanianccca3152017-02-22 16:32:36 -08001082 }
1083
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001084 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001085 public boolean shouldShowCameraPermissionToast() {
linyuh183cb712017-12-27 17:02:37 -08001086 if (primaryCall == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001087 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001088 return false;
1089 }
linyuh183cb712017-12-27 17:02:37 -08001090 if (primaryCall.didShowCameraPermission()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001091 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001092 "VideoCallPresenter.shouldShowCameraPermissionToast", "already shown for this call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001093 return false;
1094 }
linyuh183cb712017-12-27 17:02:37 -08001095 if (!ConfigProviderBindings.get(context).getBoolean("camera_permission_dialog_allowed", true)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001096 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "disabled by config");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001097 return false;
1098 }
linyuh183cb712017-12-27 17:02:37 -08001099 return !VideoUtils.hasCameraPermission(context)
1100 || !PermissionsUtil.hasCameraPrivacyToastShown(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001101 }
1102
1103 @Override
1104 public void onCameraPermissionDialogShown() {
linyuh183cb712017-12-27 17:02:37 -08001105 if (primaryCall != null) {
1106 primaryCall.setDidShowCameraPermission(true);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001107 }
1108 }
1109
Eric Erfanianccca3152017-02-22 16:32:36 -08001110 private void updateRemoteVideoSurfaceDimensions() {
linyuh183cb712017-12-27 17:02:37 -08001111 Activity activity = videoCallScreen.getVideoCallScreenFragment().getActivity();
Eric Erfanianccca3152017-02-22 16:32:36 -08001112 if (activity != null) {
1113 Point screenSize = new Point();
1114 activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
1115 getRemoteVideoSurfaceTexture().setSurfaceDimensions(screenSize);
1116 }
1117 }
1118
1119 private static boolean isVideoUpgrade(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001120 return call != null
1121 && (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
Eric Erfanianccca3152017-02-22 16:32:36 -08001122 }
1123
1124 private static boolean isVideoUpgrade(@SessionModificationState int state) {
1125 return VideoUtils.hasSentVideoUpgradeRequest(state)
1126 || VideoUtils.hasReceivedVideoUpgradeRequest(state);
1127 }
1128
1129 private class LocalDelegate implements VideoSurfaceDelegate {
1130 @Override
1131 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001132 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001133 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no UI");
1134 return;
1135 }
linyuh183cb712017-12-27 17:02:37 -08001136 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001137 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no video call");
1138 return;
1139 }
1140
1141 // If the preview surface has just been created and we have already received camera
1142 // capabilities, but not yet set the surface, we will set the surface now.
linyuh183cb712017-12-27 17:02:37 -08001143 if (previewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
1144 previewSurfaceState = PreviewSurfaceState.SURFACE_SET;
1145 videoCall.setPreviewSurface(videoCallSurface.getSavedSurface());
1146 } else if (previewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
1147 enableCamera(primaryCall, true);
Eric Erfanianccca3152017-02-22 16:32:36 -08001148 }
1149 }
1150
1151 @Override
1152 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001153 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001154 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceReleased", "no video call");
1155 return;
1156 }
1157
linyuh183cb712017-12-27 17:02:37 -08001158 videoCall.setPreviewSurface(null);
1159 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -08001160 }
1161
1162 @Override
1163 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001164 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001165 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", "no video call");
1166 return;
1167 }
1168
1169 boolean isChangingConfigurations = InCallPresenter.getInstance().isChangingConfigurations();
1170 if (!isChangingConfigurations) {
linyuh183cb712017-12-27 17:02:37 -08001171 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -08001172 } else {
1173 LogUtil.i(
1174 "VideoCallPresenter.LocalDelegate.onSurfaceDestroyed",
1175 "activity is being destroyed due to configuration changes. Not closing the camera.");
1176 }
1177 }
1178
1179 @Override
1180 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1181 VideoCallPresenter.this.onSurfaceClick();
1182 }
1183 }
1184
1185 private class RemoteDelegate implements VideoSurfaceDelegate {
1186 @Override
1187 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001188 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001189 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no UI");
1190 return;
1191 }
linyuh183cb712017-12-27 17:02:37 -08001192 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001193 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no video call");
1194 return;
1195 }
linyuh183cb712017-12-27 17:02:37 -08001196 videoCall.setDisplaySurface(videoCallSurface.getSavedSurface());
Eric Erfanianccca3152017-02-22 16:32:36 -08001197 }
1198
1199 @Override
1200 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001201 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001202 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceReleased", "no video call");
1203 return;
1204 }
linyuh183cb712017-12-27 17:02:37 -08001205 videoCall.setDisplaySurface(null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001206 }
1207
1208 @Override
1209 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {}
1210
1211 @Override
1212 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1213 VideoCallPresenter.this.onSurfaceClick();
1214 }
1215 }
1216
1217 /** Defines the state of the preview surface negotiation with the telephony layer. */
1218 private static class PreviewSurfaceState {
1219
1220 /**
1221 * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet started.
1222 */
1223 private static final int NONE = 0;
1224
1225 /**
1226 * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet been
1227 * received.
1228 */
1229 private static final int CAMERA_SET = 1;
1230
1231 /**
1232 * The camera capabilties have been received from telephony, but the surface has not yet been
1233 * set on the {@link VideoCall}.
1234 */
1235 private static final int CAPABILITIES_RECEIVED = 2;
1236
1237 /** The surface has been set on the {@link VideoCall}. */
1238 private static final int SURFACE_SET = 3;
1239 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001240
1241 private static boolean isBidirectionalVideoCall(DialerCall call) {
1242 return CompatUtils.isVideoCompatible() && VideoProfile.isBidirectional(call.getVideoState());
1243 }
1244
1245 private static boolean isIncomingVideoCall(DialerCall call) {
1246 if (!isVideoCall(call)) {
1247 return false;
1248 }
1249 final int state = call.getState();
1250 return (state == DialerCall.State.INCOMING) || (state == DialerCall.State.CALL_WAITING);
1251 }
1252
1253 private static boolean isActiveVideoCall(DialerCall call) {
1254 return isVideoCall(call) && call.getState() == DialerCall.State.ACTIVE;
1255 }
1256
1257 private static boolean isOutgoingVideoCall(DialerCall call) {
1258 if (!isVideoCall(call)) {
1259 return false;
1260 }
1261 final int state = call.getState();
1262 return DialerCall.State.isDialing(state)
1263 || state == DialerCall.State.CONNECTING
1264 || state == DialerCall.State.SELECT_PHONE_ACCOUNT;
1265 }
1266
1267 private static boolean isAudioCall(DialerCall call) {
1268 if (!CompatUtils.isVideoCompatible()) {
1269 return true;
1270 }
1271
1272 return call != null && VideoProfile.isAudioOnly(call.getVideoState());
1273 }
1274
1275 private static boolean isVideoCall(@Nullable DialerCall call) {
1276 return call != null && call.isVideoCall();
1277 }
1278
1279 private static boolean isVideoCall(int videoState) {
1280 return CompatUtils.isVideoCompatible()
1281 && (VideoProfile.isTransmissionEnabled(videoState)
1282 || VideoProfile.isReceptionEnabled(videoState));
1283 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001284}