blob: f5d681c74f24a774593716754d48c96332bdac5a [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.incallui;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.Point;
22import android.os.Handler;
wangqida410d32018-03-06 16:51:38 -080023import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080024import android.support.annotation.Nullable;
Eric Erfanianccca3152017-02-22 16:32:36 -080025import android.telecom.InCallService.VideoCall;
26import android.telecom.VideoProfile;
27import android.telecom.VideoProfile.CameraCapabilities;
28import android.view.Surface;
Eric Erfanian90508232017-03-24 09:31:16 -070029import android.view.SurfaceView;
Eric Erfanianccca3152017-02-22 16:32:36 -080030import com.android.dialer.common.Assert;
Eric Erfanianccca3152017-02-22 16:32:36 -080031import com.android.dialer.common.LogUtil;
32import com.android.dialer.compat.CompatUtils;
Eric Erfanian2ca43182017-08-31 06:57:16 -070033import com.android.dialer.configprovider.ConfigProviderBindings;
34import com.android.dialer.util.PermissionsUtil;
Eric Erfanianccca3152017-02-22 16:32:36 -080035import com.android.incallui.InCallPresenter.InCallDetailsListener;
36import com.android.incallui.InCallPresenter.InCallOrientationListener;
37import com.android.incallui.InCallPresenter.InCallStateListener;
38import com.android.incallui.InCallPresenter.IncomingCallListener;
39import com.android.incallui.call.CallList;
40import com.android.incallui.call.DialerCall;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070041import com.android.incallui.call.DialerCall.CameraDirection;
Eric Erfanianccca3152017-02-22 16:32:36 -080042import com.android.incallui.call.DialerCall.State;
43import com.android.incallui.call.InCallVideoCallCallbackNotifier;
44import com.android.incallui.call.InCallVideoCallCallbackNotifier.SurfaceChangeListener;
Eric Erfanianccca3152017-02-22 16:32:36 -080045import com.android.incallui.util.AccessibilityUtil;
46import com.android.incallui.video.protocol.VideoCallScreen;
47import com.android.incallui.video.protocol.VideoCallScreenDelegate;
48import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate;
49import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
Eric Erfanian90508232017-03-24 09:31:16 -070050import com.android.incallui.videotech.utils.SessionModificationState;
51import com.android.incallui.videotech.utils.VideoUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import java.util.Objects;
53
54/**
55 * Logic related to the {@link VideoCallScreen} and for managing changes to the video calling
56 * surfaces based on other user interface events and incoming events from the {@class
57 * VideoCallListener}.
58 *
59 * <p>When a call's video state changes to bi-directional video, the {@link
60 * com.android.incallui.VideoCallPresenter} performs the following negotiation with the telephony
61 * layer:
62 *
63 * <ul>
wangqi385a5a12017-09-28 10:44:54 -070064 * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.
65 * <li>{@code VideoCallPresenter} creates the preview surface.
66 * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.
67 * <li>Telephony layer sends {@link CameraCapabilities}, including the dimensions of the video for
68 * the current camera.
69 * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect ratio of
70 * the camera.
71 * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.
Eric Erfanianccca3152017-02-22 16:32:36 -080072 * </ul>
73 *
74 * <p>When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both
75 * surfaces.
76 */
77public class VideoCallPresenter
78 implements IncomingCallListener,
79 InCallOrientationListener,
80 InCallStateListener,
81 InCallDetailsListener,
82 SurfaceChangeListener,
Eric Erfanianccca3152017-02-22 16:32:36 -080083 InCallPresenter.InCallEventListener,
wangqida410d32018-03-06 16:51:38 -080084 VideoCallScreenDelegate,
85 CallList.Listener {
Eric Erfanianccca3152017-02-22 16:32:36 -080086
linyuh183cb712017-12-27 17:02:37 -080087 private static boolean isVideoMode = false;
Eric Erfanianccca3152017-02-22 16:32:36 -080088
linyuh183cb712017-12-27 17:02:37 -080089 private final Handler handler = new Handler();
90 private VideoCallScreen videoCallScreen;
Eric Erfanianccca3152017-02-22 16:32:36 -080091
92 /** The current context. */
linyuh183cb712017-12-27 17:02:37 -080093 private Context context;
Eric Erfanianccca3152017-02-22 16:32:36 -080094
Eric Erfanianccca3152017-02-22 16:32:36 -080095 /** The call the video surfaces are currently related to */
linyuh183cb712017-12-27 17:02:37 -080096 private DialerCall primaryCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080097 /**
98 * The {@link VideoCall} used to inform the video telephony layer of changes to the video
99 * surfaces.
100 */
linyuh183cb712017-12-27 17:02:37 -0800101 private VideoCall videoCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800102 /** Determines if the current UI state represents a video call. */
linyuh183cb712017-12-27 17:02:37 -0800103 private int currentVideoState;
Eric Erfanianccca3152017-02-22 16:32:36 -0800104 /** DialerCall's current state */
linyuh183cb712017-12-27 17:02:37 -0800105 private int currentCallState = DialerCall.State.INVALID;
Eric Erfanianccca3152017-02-22 16:32:36 -0800106 /** Determines the device orientation (portrait/lanscape). */
linyuh183cb712017-12-27 17:02:37 -0800107 private int deviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800108 /** Tracks the state of the preview surface negotiation with the telephony layer. */
linyuh183cb712017-12-27 17:02:37 -0800109 private int previewSurfaceState = PreviewSurfaceState.NONE;
Eric Erfanianccca3152017-02-22 16:32:36 -0800110 /**
111 * Determines whether video calls should automatically enter full screen mode after {@link
linyuh183cb712017-12-27 17:02:37 -0800112 * #autoFullscreenTimeoutMillis} milliseconds.
Eric Erfanianccca3152017-02-22 16:32:36 -0800113 */
linyuh183cb712017-12-27 17:02:37 -0800114 private boolean isAutoFullscreenEnabled = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800115 /**
116 * Determines the number of milliseconds after which a video call will automatically enter
linyuh183cb712017-12-27 17:02:37 -0800117 * fullscreen mode. Requires {@link #isAutoFullscreenEnabled} to be {@code true}.
Eric Erfanianccca3152017-02-22 16:32:36 -0800118 */
linyuh183cb712017-12-27 17:02:37 -0800119 private int autoFullscreenTimeoutMillis = 0;
Eric Erfanianccca3152017-02-22 16:32:36 -0800120 /**
121 * Determines if the countdown is currently running to automatically enter full screen video mode.
122 */
linyuh183cb712017-12-27 17:02:37 -0800123 private boolean autoFullScreenPending = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800124 /** Whether if the call is remotely held. */
linyuh183cb712017-12-27 17:02:37 -0800125 private boolean isRemotelyHeld = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800126 /**
127 * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto
128 * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit the
129 * dialpad).
130 */
linyuh183cb712017-12-27 17:02:37 -0800131 private Runnable autoFullscreenRunnable =
Eric Erfanianccca3152017-02-22 16:32:36 -0800132 new Runnable() {
133 @Override
134 public void run() {
linyuh183cb712017-12-27 17:02:37 -0800135 if (autoFullScreenPending
Eric Erfanianccca3152017-02-22 16:32:36 -0800136 && !InCallPresenter.getInstance().isDialpadVisible()
linyuh183cb712017-12-27 17:02:37 -0800137 && isVideoMode) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800138
139 LogUtil.v("VideoCallPresenter.mAutoFullScreenRunnable", "entering fullscreen mode");
140 InCallPresenter.getInstance().setFullScreen(true);
linyuh183cb712017-12-27 17:02:37 -0800141 autoFullScreenPending = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800142 } else {
143 LogUtil.v(
144 "VideoCallPresenter.mAutoFullScreenRunnable",
145 "skipping scheduled fullscreen mode.");
146 }
147 }
148 };
149
150 private boolean isVideoCallScreenUiReady;
151
152 private static boolean isCameraRequired(int videoState, int sessionModificationState) {
153 return VideoProfile.isBidirectional(videoState)
154 || VideoProfile.isTransmissionEnabled(videoState)
155 || isVideoUpgrade(sessionModificationState);
156 }
157
158 /**
159 * Determines if the incoming video surface should be shown based on the current videoState and
Eric Erfaniand8046e52017-04-06 09:41:50 -0700160 * callState. The video surface is shown when incoming video is not paused, the call is active or
161 * dialing and video reception is enabled.
Eric Erfanianccca3152017-02-22 16:32:36 -0800162 *
163 * @param videoState The current video state.
164 * @param callState The current call state.
165 * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise.
166 */
167 public static boolean showIncomingVideo(int videoState, int callState) {
168 if (!CompatUtils.isVideoCompatible()) {
169 return false;
170 }
171
172 boolean isPaused = VideoProfile.isPaused(videoState);
173 boolean isCallActive = callState == DialerCall.State.ACTIVE;
wangqi385a5a12017-09-28 10:44:54 -0700174 // Show incoming Video for dialing calls to support early media
Eric Erfaniand8046e52017-04-06 09:41:50 -0700175 boolean isCallOutgoingPending =
176 DialerCall.State.isDialing(callState) || callState == DialerCall.State.CONNECTING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800177
Eric Erfaniand8046e52017-04-06 09:41:50 -0700178 return !isPaused
179 && (isCallActive || isCallOutgoingPending)
180 && VideoProfile.isReceptionEnabled(videoState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800181 }
182
183 /**
184 * Determines if the outgoing video surface should be shown based on the current videoState. The
185 * video surface is shown if video transmission is enabled.
186 *
187 * @return {@code true} if the the outgoing video surface should be shown, {@code false}
188 * otherwise.
189 */
190 public static boolean showOutgoingVideo(
191 Context context, int videoState, int sessionModificationState) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700192 if (!VideoUtils.hasCameraPermissionAndShownPrivacyToast(context)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800193 LogUtil.i("VideoCallPresenter.showOutgoingVideo", "Camera permission is disabled by user.");
194 return false;
195 }
196
197 if (!CompatUtils.isVideoCompatible()) {
198 return false;
199 }
200
201 return VideoProfile.isTransmissionEnabled(videoState)
202 || isVideoUpgrade(sessionModificationState);
203 }
204
205 private static void updateCameraSelection(DialerCall call) {
206 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + call);
207 LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + toSimpleString(call));
208
209 final DialerCall activeCall = CallList.getInstance().getActiveCall();
210 int cameraDir;
211
212 // this function should never be called with null call object, however if it happens we
213 // should handle it gracefully.
214 if (call == null) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700215 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800216 LogUtil.e(
217 "VideoCallPresenter.updateCameraSelection",
218 "call is null. Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
219 }
220
221 // Clear camera direction if this is not a video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700222 else if (isAudioCall(call) && !isVideoUpgrade(call)) {
223 cameraDir = CameraDirection.CAMERA_DIRECTION_UNKNOWN;
224 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800225 }
226
227 // If this is a waiting video call, default to active call's camera,
228 // since we don't want to change the current camera for waiting call
229 // without user's permission.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700230 else if (isVideoCall(activeCall) && isIncomingVideoCall(call)) {
231 cameraDir = activeCall.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 }
233
234 // Infer the camera direction from the video state and store it,
235 // if this is an outgoing video call.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700236 else if (isOutgoingVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800237 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700238 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800239 }
240
241 // Use the stored camera dir if this is an outgoing video call for which camera direction
242 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700243 else if (isOutgoingVideoCall(call)) {
244 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800245 }
246
247 // Infer the camera direction from the video state and store it,
248 // if this is an active video call and camera direction is not set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700249 else if (isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800250 cameraDir = toCameraDirection(call.getVideoState());
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700251 call.setCameraDir(cameraDir);
Eric Erfanianccca3152017-02-22 16:32:36 -0800252 }
253
254 // Use the stored camera dir if this is an active video call for which camera direction
255 // is set.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700256 else if (isActiveVideoCall(call)) {
257 cameraDir = call.getCameraDir();
Eric Erfanianccca3152017-02-22 16:32:36 -0800258 }
259
260 // For all other cases infer the camera direction but don't store it in the call object.
261 else {
262 cameraDir = toCameraDirection(call.getVideoState());
263 }
264
265 LogUtil.i(
266 "VideoCallPresenter.updateCameraSelection",
267 "setting camera direction to %d, call: %s",
268 cameraDir,
269 call);
270 final InCallCameraManager cameraManager =
271 InCallPresenter.getInstance().getInCallCameraManager();
272 cameraManager.setUseFrontFacingCamera(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700273 cameraDir == CameraDirection.CAMERA_DIRECTION_FRONT_FACING);
Eric Erfanianccca3152017-02-22 16:32:36 -0800274 }
275
276 private static int toCameraDirection(int videoState) {
277 return VideoProfile.isTransmissionEnabled(videoState)
278 && !VideoProfile.isBidirectional(videoState)
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700279 ? CameraDirection.CAMERA_DIRECTION_BACK_FACING
280 : CameraDirection.CAMERA_DIRECTION_FRONT_FACING;
Eric Erfanianccca3152017-02-22 16:32:36 -0800281 }
282
283 private static boolean isCameraDirectionSet(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700284 return isVideoCall(call) && call.getCameraDir() != CameraDirection.CAMERA_DIRECTION_UNKNOWN;
Eric Erfanianccca3152017-02-22 16:32:36 -0800285 }
286
287 private static String toSimpleString(DialerCall call) {
288 return call == null ? null : call.toSimpleString();
289 }
290
291 /**
292 * Initializes the presenter.
293 *
294 * @param context The current context.
295 */
296 @Override
297 public void initVideoCallScreenDelegate(Context context, VideoCallScreen videoCallScreen) {
linyuh183cb712017-12-27 17:02:37 -0800298 this.context = context;
299 this.videoCallScreen = videoCallScreen;
300 isAutoFullscreenEnabled =
301 this.context.getResources().getBoolean(R.bool.video_call_auto_fullscreen);
302 autoFullscreenTimeoutMillis =
303 this.context.getResources().getInteger(R.integer.video_call_auto_fullscreen_timeout);
Eric Erfanianccca3152017-02-22 16:32:36 -0800304 }
305
306 /** Called when the user interface is ready to be used. */
307 @Override
308 public void onVideoCallScreenUiReady() {
309 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiReady", "");
310 Assert.checkState(!isVideoCallScreenUiReady);
311
312 // Do not register any listeners if video calling is not compatible to safeguard against
313 // any accidental calls of video calling code.
314 if (!CompatUtils.isVideoCompatible()) {
315 return;
316 }
317
linyuh183cb712017-12-27 17:02:37 -0800318 deviceOrientation = InCallOrientationEventListener.getCurrentOrientation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800319
320 // Register for call state changes last
321 InCallPresenter.getInstance().addListener(this);
322 InCallPresenter.getInstance().addDetailsListener(this);
323 InCallPresenter.getInstance().addIncomingCallListener(this);
324 InCallPresenter.getInstance().addOrientationListener(this);
325 // To get updates of video call details changes
326 InCallPresenter.getInstance().addInCallEventListener(this);
327 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate());
328 InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate());
329
wangqida410d32018-03-06 16:51:38 -0800330 CallList.getInstance().addListener(this);
331
Eric Erfanianccca3152017-02-22 16:32:36 -0800332 // Register for surface and video events from {@link InCallVideoCallListener}s.
333 InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
linyuh183cb712017-12-27 17:02:37 -0800334 currentVideoState = VideoProfile.STATE_AUDIO_ONLY;
335 currentCallState = DialerCall.State.INVALID;
Eric Erfanianccca3152017-02-22 16:32:36 -0800336
337 InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState();
338 onStateChange(inCallState, inCallState, CallList.getInstance());
339 isVideoCallScreenUiReady = true;
340 }
341
342 /** Called when the user interface is no longer ready to be used. */
343 @Override
344 public void onVideoCallScreenUiUnready() {
345 LogUtil.v("VideoCallPresenter.onVideoCallScreenUiUnready", "");
346 Assert.checkState(isVideoCallScreenUiReady);
347
348 if (!CompatUtils.isVideoCompatible()) {
349 return;
350 }
351
352 cancelAutoFullScreen();
353
354 InCallPresenter.getInstance().removeListener(this);
355 InCallPresenter.getInstance().removeDetailsListener(this);
356 InCallPresenter.getInstance().removeIncomingCallListener(this);
357 InCallPresenter.getInstance().removeOrientationListener(this);
358 InCallPresenter.getInstance().removeInCallEventListener(this);
359 InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null);
360
wangqida410d32018-03-06 16:51:38 -0800361 CallList.getInstance().removeListener(this);
362
Eric Erfanianccca3152017-02-22 16:32:36 -0800363 InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800364
365 // Ensure that the call's camera direction is updated (most likely to UNKNOWN). Normally this
366 // happens after any call state changes but we're unregistering from InCallPresenter above so
Eric Erfanian938468d2017-10-24 14:05:52 -0700367 // we won't get any more call state changes. See a bug.
linyuh183cb712017-12-27 17:02:37 -0800368 if (primaryCall != null) {
369 updateCameraSelection(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800370 }
371
372 isVideoCallScreenUiReady = false;
373 }
374
375 /**
376 * Handles clicks on the video surfaces. If not currently in fullscreen mode, will set fullscreen.
377 */
378 private void onSurfaceClick() {
379 LogUtil.i("VideoCallPresenter.onSurfaceClick", "");
380 cancelAutoFullScreen();
381 if (!InCallPresenter.getInstance().isFullscreen()) {
382 InCallPresenter.getInstance().setFullScreen(true);
383 } else {
384 InCallPresenter.getInstance().setFullScreen(false);
linyuh183cb712017-12-27 17:02:37 -0800385 maybeAutoEnterFullscreen(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800386 // If Activity is not multiwindow, fullscreen will be driven by SystemUI visibility changes
387 // instead. See #onSystemUiVisibilityChange(boolean)
388
389 // TODO (keyboardr): onSystemUiVisibilityChange isn't being called the first time
390 // visibility changes after orientation change, so this is currently always done as a backup.
391 }
392 }
393
394 @Override
395 public void onSystemUiVisibilityChange(boolean visible) {
396 // If the SystemUI has changed to be visible, take us out of fullscreen mode
397 LogUtil.i("VideoCallPresenter.onSystemUiVisibilityChange", "visible: " + visible);
398 if (visible) {
399 InCallPresenter.getInstance().setFullScreen(false);
linyuh183cb712017-12-27 17:02:37 -0800400 maybeAutoEnterFullscreen(primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800401 }
402 }
403
404 @Override
405 public VideoSurfaceTexture getLocalVideoSurfaceTexture() {
406 return InCallPresenter.getInstance().getLocalVideoSurfaceTexture();
407 }
408
409 @Override
410 public VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
411 return InCallPresenter.getInstance().getRemoteVideoSurfaceTexture();
412 }
413
414 @Override
Eric Erfanian90508232017-03-24 09:31:16 -0700415 public void setSurfaceViews(SurfaceView preview, SurfaceView remote) {
416 throw Assert.createUnsupportedOperationFailException();
417 }
418
419 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -0800420 public int getDeviceOrientation() {
linyuh183cb712017-12-27 17:02:37 -0800421 return deviceOrientation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800422 }
423
424 /**
425 * This should only be called when user approved the camera permission, which is local action and
426 * does NOT change any call states.
427 */
428 @Override
429 public void onCameraPermissionGranted() {
430 LogUtil.i("VideoCallPresenter.onCameraPermissionGranted", "");
linyuh183cb712017-12-27 17:02:37 -0800431 PermissionsUtil.setCameraPrivacyToastShown(context);
432 enableCamera(primaryCall, isCameraRequired());
Eric Erfanianccca3152017-02-22 16:32:36 -0800433 showVideoUi(
linyuh183cb712017-12-27 17:02:37 -0800434 primaryCall.getVideoState(),
435 primaryCall.getState(),
436 primaryCall.getVideoTech().getSessionModificationState(),
437 primaryCall.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800438 InCallPresenter.getInstance().getInCallCameraManager().onCameraPermissionGranted();
439 }
440
441 /**
442 * Called when the user interacts with the UI. If a fullscreen timer is pending then we start the
443 * timer from scratch to avoid having the UI disappear while the user is interacting with it.
444 */
445 @Override
446 public void resetAutoFullscreenTimer() {
linyuh183cb712017-12-27 17:02:37 -0800447 if (autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800448 LogUtil.i("VideoCallPresenter.resetAutoFullscreenTimer", "resetting");
linyuh183cb712017-12-27 17:02:37 -0800449 handler.removeCallbacks(autoFullscreenRunnable);
450 handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis);
Eric Erfanianccca3152017-02-22 16:32:36 -0800451 }
452 }
453
454 /**
455 * Handles incoming calls.
456 *
457 * @param oldState The old in call state.
458 * @param newState The new in call state.
459 * @param call The call.
460 */
461 @Override
462 public void onIncomingCall(
463 InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, DialerCall call) {
wangqi385a5a12017-09-28 10:44:54 -0700464 // If video call screen ui is already destroyed, this shouldn't be called. But the UI may be
465 // updated synchronized by {@link CallCardPresenter#onIncomingCall} before this is called, this
466 // could still be called. Thus just do nothing in this case.
467 if (!isVideoCallScreenUiReady) {
468 LogUtil.i("VideoCallPresenter.onIncomingCall", "UI is not ready");
469 return;
470 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800471 // same logic should happen as with onStateChange()
472 onStateChange(oldState, newState, CallList.getInstance());
473 }
474
475 /**
476 * Handles state changes (including incoming calls)
477 *
478 * @param newState The in call state.
479 * @param callList The call list.
480 */
481 @Override
482 public void onStateChange(
483 InCallPresenter.InCallState oldState,
484 InCallPresenter.InCallState newState,
485 CallList callList) {
486 LogUtil.v(
487 "VideoCallPresenter.onStateChange",
488 "oldState: %s, newState: %s, isVideoMode: %b",
489 oldState,
490 newState,
491 isVideoMode());
492
493 if (newState == InCallPresenter.InCallState.NO_CALLS) {
494 if (isVideoMode()) {
495 exitVideoMode();
496 }
497
498 InCallPresenter.getInstance().cleanupSurfaces();
499 }
500
501 // Determine the primary active call).
502 DialerCall primary = null;
503
504 // Determine the call which is the focus of the user's attention. In the case of an
505 // incoming call waiting call, the primary call is still the active video call, however
506 // the determination of whether we should be in fullscreen mode is based on the type of the
507 // incoming call, not the active video call.
508 DialerCall currentCall = null;
509
510 if (newState == InCallPresenter.InCallState.INCOMING) {
511 // We don't want to replace active video call (primary call)
512 // with a waiting call, since user may choose to ignore/decline the waiting call and
513 // this should have no impact on current active video call, that is, we should not
514 // change the camera or UI unless the waiting VT call becomes active.
515 primary = callList.getActiveCall();
516 currentCall = callList.getIncomingCall();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700517 if (!isActiveVideoCall(primary)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800518 primary = callList.getIncomingCall();
519 }
520 } else if (newState == InCallPresenter.InCallState.OUTGOING) {
521 currentCall = primary = callList.getOutgoingCall();
522 } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
523 currentCall = primary = callList.getPendingOutgoingCall();
524 } else if (newState == InCallPresenter.InCallState.INCALL) {
525 currentCall = primary = callList.getActiveCall();
526 }
527
linyuh183cb712017-12-27 17:02:37 -0800528 final boolean primaryChanged = !Objects.equals(primaryCall, primary);
Eric Erfanianccca3152017-02-22 16:32:36 -0800529 LogUtil.i(
530 "VideoCallPresenter.onStateChange",
531 "primaryChanged: %b, primary: %s, mPrimaryCall: %s",
532 primaryChanged,
533 primary,
linyuh183cb712017-12-27 17:02:37 -0800534 primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800535 if (primaryChanged) {
536 onPrimaryCallChanged(primary);
linyuh183cb712017-12-27 17:02:37 -0800537 } else if (primaryCall != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800538 updateVideoCall(primary);
539 }
540 updateCallCache(primary);
541
542 // If the call context changed, potentially exit fullscreen or schedule auto enter of
543 // fullscreen mode.
544 // If the current call context is no longer a video call, exit fullscreen mode.
545 maybeExitFullscreen(currentCall);
546 // Schedule auto-enter of fullscreen mode if the current call context is a video call
547 maybeAutoEnterFullscreen(currentCall);
548 }
549
550 /**
551 * Handles a change to the fullscreen mode of the app.
552 *
553 * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise.
554 */
555 @Override
556 public void onFullscreenModeChanged(boolean isFullscreenMode) {
557 cancelAutoFullScreen();
linyuh183cb712017-12-27 17:02:37 -0800558 if (primaryCall != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800559 updateFullscreenAndGreenScreenMode(
linyuh183cb712017-12-27 17:02:37 -0800560 primaryCall.getState(), primaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800561 } else {
Eric Erfanian90508232017-03-24 09:31:16 -0700562 updateFullscreenAndGreenScreenMode(State.INVALID, SessionModificationState.NO_REQUEST);
Eric Erfanianccca3152017-02-22 16:32:36 -0800563 }
564 }
565
566 private void checkForVideoStateChange(DialerCall call) {
567 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
linyuh183cb712017-12-27 17:02:37 -0800568 final boolean hasVideoStateChanged = currentVideoState != call.getVideoState();
Eric Erfanianccca3152017-02-22 16:32:36 -0800569
570 LogUtil.v(
571 "VideoCallPresenter.checkForVideoStateChange",
572 "shouldShowVideoUi: %b, hasVideoStateChanged: %b, isVideoMode: %b, previousVideoState: %s,"
573 + " newVideoState: %s",
574 shouldShowVideoUi,
575 hasVideoStateChanged,
576 isVideoMode(),
linyuh183cb712017-12-27 17:02:37 -0800577 VideoProfile.videoStateToString(currentVideoState),
Eric Erfanianccca3152017-02-22 16:32:36 -0800578 VideoProfile.videoStateToString(call.getVideoState()));
579 if (!hasVideoStateChanged) {
580 return;
581 }
582
583 updateCameraSelection(call);
584
585 if (shouldShowVideoUi) {
586 adjustVideoMode(call);
587 } else if (isVideoMode()) {
588 exitVideoMode();
589 }
590 }
591
592 private void checkForCallStateChange(DialerCall call) {
593 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
594 final boolean hasCallStateChanged =
linyuh183cb712017-12-27 17:02:37 -0800595 currentCallState != call.getState() || isRemotelyHeld != call.isRemotelyHeld();
596 isRemotelyHeld = call.isRemotelyHeld();
Eric Erfanianccca3152017-02-22 16:32:36 -0800597
598 LogUtil.v(
599 "VideoCallPresenter.checkForCallStateChange",
600 "shouldShowVideoUi: %b, hasCallStateChanged: %b, isVideoMode: %b",
601 shouldShowVideoUi,
602 hasCallStateChanged,
603 isVideoMode());
604
605 if (!hasCallStateChanged) {
606 return;
607 }
608
609 if (shouldShowVideoUi) {
610 final InCallCameraManager cameraManager =
611 InCallPresenter.getInstance().getInCallCameraManager();
612
613 String prevCameraId = cameraManager.getActiveCameraId();
614 updateCameraSelection(call);
615 String newCameraId = cameraManager.getActiveCameraId();
616
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700617 if (!Objects.equals(prevCameraId, newCameraId) && isActiveVideoCall(call)) {
roldenburgca475472017-10-25 13:00:42 -0700618 enableCamera(call, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800619 }
620 }
621
622 // Make sure we hide or show the video UI if needed.
623 showVideoUi(
624 call.getVideoState(),
625 call.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700626 call.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800627 call.isRemotelyHeld());
628 }
629
630 private void onPrimaryCallChanged(DialerCall newPrimaryCall) {
631 final boolean shouldShowVideoUi = shouldShowVideoUiForCall(newPrimaryCall);
632 final boolean isVideoMode = isVideoMode();
633
634 LogUtil.v(
635 "VideoCallPresenter.onPrimaryCallChanged",
636 "shouldShowVideoUi: %b, isVideoMode: %b",
637 shouldShowVideoUi,
638 isVideoMode);
639
640 if (!shouldShowVideoUi && isVideoMode) {
641 // Terminate video mode if new primary call is not a video call
642 // and we are currently in video mode.
643 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "exiting video mode...");
644 exitVideoMode();
645 } else if (shouldShowVideoUi) {
646 LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "entering video mode...");
647
648 updateCameraSelection(newPrimaryCall);
649 adjustVideoMode(newPrimaryCall);
650 }
651 checkForOrientationAllowedChange(newPrimaryCall);
652 }
653
654 private boolean isVideoMode() {
linyuh183cb712017-12-27 17:02:37 -0800655 return isVideoMode;
Eric Erfanianccca3152017-02-22 16:32:36 -0800656 }
657
658 private void updateCallCache(DialerCall call) {
659 if (call == null) {
linyuh183cb712017-12-27 17:02:37 -0800660 currentVideoState = VideoProfile.STATE_AUDIO_ONLY;
661 currentCallState = DialerCall.State.INVALID;
662 videoCall = null;
663 primaryCall = null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800664 } else {
linyuh183cb712017-12-27 17:02:37 -0800665 currentVideoState = call.getVideoState();
666 videoCall = call.getVideoCall();
667 currentCallState = call.getState();
668 primaryCall = call;
Eric Erfanianccca3152017-02-22 16:32:36 -0800669 }
670 }
671
672 /**
673 * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in
674 * changes to the video state.
675 *
676 * @param call The call for which the details changed.
677 * @param details The new call details.
678 */
679 @Override
680 public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) {
681 LogUtil.v(
682 "VideoCallPresenter.onDetailsChanged",
683 "call: %s, details: %s, mPrimaryCall: %s",
684 call,
685 details,
linyuh183cb712017-12-27 17:02:37 -0800686 primaryCall);
Eric Erfanianccca3152017-02-22 16:32:36 -0800687 if (call == null) {
688 return;
689 }
690 // If the details change is not for the currently active call no update is required.
linyuh183cb712017-12-27 17:02:37 -0800691 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800692 LogUtil.v("VideoCallPresenter.onDetailsChanged", "details not for current active call");
693 return;
694 }
695
696 updateVideoCall(call);
697
698 updateCallCache(call);
699 }
700
701 private void updateVideoCall(DialerCall call) {
702 checkForVideoCallChange(call);
703 checkForVideoStateChange(call);
704 checkForCallStateChange(call);
705 checkForOrientationAllowedChange(call);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700706 updateFullscreenAndGreenScreenMode(
707 call.getState(), call.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 }
709
710 private void checkForOrientationAllowedChange(@Nullable DialerCall call) {
711 InCallPresenter.getInstance()
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700712 .setInCallAllowsOrientationChange(isVideoCall(call) || isVideoUpgrade(call));
Eric Erfanianccca3152017-02-22 16:32:36 -0800713 }
714
715 private void updateFullscreenAndGreenScreenMode(
716 int callState, @SessionModificationState int sessionModificationState) {
linyuh183cb712017-12-27 17:02:37 -0800717 if (videoCallScreen != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800718 boolean shouldShowFullscreen = InCallPresenter.getInstance().isFullscreen();
719 boolean shouldShowGreenScreen =
720 callState == State.DIALING
721 || callState == State.CONNECTING
722 || callState == State.INCOMING
723 || isVideoUpgrade(sessionModificationState);
linyuh183cb712017-12-27 17:02:37 -0800724 videoCallScreen.updateFullscreenAndGreenScreenMode(
Eric Erfanianccca3152017-02-22 16:32:36 -0800725 shouldShowFullscreen, shouldShowGreenScreen);
726 }
727 }
728
729 /** Checks for a change to the video call and changes it if required. */
730 private void checkForVideoCallChange(DialerCall call) {
731 final VideoCall videoCall = call.getVideoCall();
732 LogUtil.v(
733 "VideoCallPresenter.checkForVideoCallChange",
734 "videoCall: %s, mVideoCall: %s",
735 videoCall,
linyuh183cb712017-12-27 17:02:37 -0800736 this.videoCall);
737 if (!Objects.equals(videoCall, this.videoCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 changeVideoCall(call);
739 }
740 }
741
742 /**
743 * Handles a change to the video call. Sets the surfaces on the previous call to null and sets the
744 * surfaces on the new video call accordingly.
745 *
746 * @param call The new video call.
747 */
748 private void changeVideoCall(DialerCall call) {
749 final VideoCall videoCall = call == null ? null : call.getVideoCall();
750 LogUtil.i(
751 "VideoCallPresenter.changeVideoCall",
752 "videoCall: %s, mVideoCall: %s",
753 videoCall,
linyuh183cb712017-12-27 17:02:37 -0800754 this.videoCall);
755 final boolean hasChanged = this.videoCall == null && videoCall != null;
Eric Erfanianccca3152017-02-22 16:32:36 -0800756
linyuh183cb712017-12-27 17:02:37 -0800757 this.videoCall = videoCall;
758 if (this.videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800759 LogUtil.v("VideoCallPresenter.changeVideoCall", "video call or primary call is null. Return");
760 return;
761 }
762
763 if (shouldShowVideoUiForCall(call) && hasChanged) {
764 adjustVideoMode(call);
765 }
766 }
767
768 private boolean isCameraRequired() {
linyuh183cb712017-12-27 17:02:37 -0800769 return primaryCall != null
Eric Erfanianccca3152017-02-22 16:32:36 -0800770 && isCameraRequired(
linyuh183cb712017-12-27 17:02:37 -0800771 primaryCall.getVideoState(), primaryCall.getVideoTech().getSessionModificationState());
Eric Erfanianccca3152017-02-22 16:32:36 -0800772 }
773
774 /**
775 * Adjusts the current video mode by setting up the preview and display surfaces as necessary.
776 * Expected to be called whenever the video state associated with a call changes (e.g. a user
Eric Erfanian2ca43182017-08-31 06:57:16 -0700777 * turns their camera on or off) to ensure the correct surfaces are shown/hidden. TODO(vt): Need
Eric Erfanianccca3152017-02-22 16:32:36 -0800778 * to adjust size and orientation of preview surface here.
779 */
780 private void adjustVideoMode(DialerCall call) {
781 VideoCall videoCall = call.getVideoCall();
782 int newVideoState = call.getVideoState();
783
784 LogUtil.i(
785 "VideoCallPresenter.adjustVideoMode",
786 "videoCall: %s, videoState: %d",
787 videoCall,
788 newVideoState);
linyuh183cb712017-12-27 17:02:37 -0800789 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800790 LogUtil.e("VideoCallPresenter.adjustVideoMode", "error VideoCallScreen is null so returning");
791 return;
792 }
793
794 showVideoUi(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700795 newVideoState,
796 call.getState(),
797 call.getVideoTech().getSessionModificationState(),
798 call.isRemotelyHeld());
Eric Erfanianccca3152017-02-22 16:32:36 -0800799
800 // Communicate the current camera to telephony and make a request for the camera
801 // capabilities.
802 if (videoCall != null) {
803 Surface surface = getRemoteVideoSurfaceTexture().getSavedSurface();
804 if (surface != null) {
805 LogUtil.v(
806 "VideoCallPresenter.adjustVideoMode", "calling setDisplaySurface with: " + surface);
807 videoCall.setDisplaySurface(surface);
808 }
809
810 Assert.checkState(
linyuh183cb712017-12-27 17:02:37 -0800811 deviceOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN);
812 videoCall.setDeviceOrientation(deviceOrientation);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700813 enableCamera(
roldenburgca475472017-10-25 13:00:42 -0700814 call, isCameraRequired(newVideoState, call.getVideoTech().getSessionModificationState()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800815 }
linyuh183cb712017-12-27 17:02:37 -0800816 int previousVideoState = currentVideoState;
817 currentVideoState = newVideoState;
818 isVideoMode = true;
Eric Erfanianccca3152017-02-22 16:32:36 -0800819
820 // adjustVideoMode may be called if we are already in a 1-way video state. In this case
821 // we do not want to trigger auto-fullscreen mode.
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700822 if (!isVideoCall(previousVideoState) && isVideoCall(newVideoState)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800823 maybeAutoEnterFullscreen(call);
824 }
825 }
826
827 private static boolean shouldShowVideoUiForCall(@Nullable DialerCall call) {
828 if (call == null) {
829 return false;
830 }
831
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700832 if (isVideoCall(call)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800833 return true;
834 }
835
836 if (isVideoUpgrade(call)) {
837 return true;
838 }
839
840 return false;
841 }
842
roldenburgca475472017-10-25 13:00:42 -0700843 private void enableCamera(DialerCall call, boolean isCameraRequired) {
844 LogUtil.v("VideoCallPresenter.enableCamera", "call: %s, enabling: %b", call, isCameraRequired);
845 if (call == null) {
846 LogUtil.i("VideoCallPresenter.enableCamera", "call is null");
Eric Erfanianccca3152017-02-22 16:32:36 -0800847 return;
848 }
849
linyuh183cb712017-12-27 17:02:37 -0800850 boolean hasCameraPermission = VideoUtils.hasCameraPermissionAndShownPrivacyToast(context);
Eric Erfanianccca3152017-02-22 16:32:36 -0800851 if (!hasCameraPermission) {
roldenburgca475472017-10-25 13:00:42 -0700852 call.getVideoTech().setCamera(null);
linyuh183cb712017-12-27 17:02:37 -0800853 previewSurfaceState = PreviewSurfaceState.NONE;
Eric Erfanian938468d2017-10-24 14:05:52 -0700854 // TODO(wangqi): Inform remote party that the video is off. This is similar to a bug.
Eric Erfanianccca3152017-02-22 16:32:36 -0800855 } else if (isCameraRequired) {
856 InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
roldenburgca475472017-10-25 13:00:42 -0700857 call.getVideoTech().setCamera(cameraManager.getActiveCameraId());
linyuh183cb712017-12-27 17:02:37 -0800858 previewSurfaceState = PreviewSurfaceState.CAMERA_SET;
Eric Erfanianccca3152017-02-22 16:32:36 -0800859 } else {
linyuh183cb712017-12-27 17:02:37 -0800860 previewSurfaceState = PreviewSurfaceState.NONE;
roldenburgca475472017-10-25 13:00:42 -0700861 call.getVideoTech().setCamera(null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800862 }
863 }
864
865 /** Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */
866 private void exitVideoMode() {
867 LogUtil.i("VideoCallPresenter.exitVideoMode", "");
868
869 showVideoUi(
870 VideoProfile.STATE_AUDIO_ONLY,
871 DialerCall.State.ACTIVE,
Eric Erfanian90508232017-03-24 09:31:16 -0700872 SessionModificationState.NO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 false /* isRemotelyHeld */);
linyuh183cb712017-12-27 17:02:37 -0800874 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800875 InCallPresenter.getInstance().setFullScreen(false);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700876 InCallPresenter.getInstance().enableScreenTimeout(false);
linyuh183cb712017-12-27 17:02:37 -0800877 isVideoMode = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800878 }
879
880 /**
881 * Based on the current video state and call state, show or hide the incoming and outgoing video
882 * surfaces. The outgoing video surface is shown any time video is transmitting. The incoming
883 * video surface is shown whenever the video is un-paused and active.
884 *
885 * @param videoState The video state.
886 * @param callState The call state.
887 */
888 private void showVideoUi(
889 int videoState,
890 int callState,
891 @SessionModificationState int sessionModificationState,
892 boolean isRemotelyHeld) {
linyuh183cb712017-12-27 17:02:37 -0800893 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800894 LogUtil.e("VideoCallPresenter.showVideoUi", "videoCallScreen is null returning");
895 return;
896 }
897 boolean showIncomingVideo = showIncomingVideo(videoState, callState);
linyuh183cb712017-12-27 17:02:37 -0800898 boolean showOutgoingVideo = showOutgoingVideo(context, videoState, sessionModificationState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800899 LogUtil.i(
900 "VideoCallPresenter.showVideoUi",
901 "showIncoming: %b, showOutgoing: %b, isRemotelyHeld: %b",
902 showIncomingVideo,
903 showOutgoingVideo,
904 isRemotelyHeld);
905 updateRemoteVideoSurfaceDimensions();
linyuh183cb712017-12-27 17:02:37 -0800906 videoCallScreen.showVideoViews(showOutgoingVideo, showIncomingVideo, isRemotelyHeld);
Eric Erfanianccca3152017-02-22 16:32:36 -0800907
908 InCallPresenter.getInstance().enableScreenTimeout(VideoProfile.isAudioOnly(videoState));
909 updateFullscreenAndGreenScreenMode(callState, sessionModificationState);
910 }
911
912 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800913 * Handles peer video dimension changes.
914 *
915 * @param call The call which experienced a peer video dimension change.
916 * @param width The new peer video width .
917 * @param height The new peer video height.
918 */
919 @Override
920 public void onUpdatePeerDimensions(DialerCall call, int width, int height) {
921 LogUtil.i("VideoCallPresenter.onUpdatePeerDimensions", "width: %d, height: %d", width, height);
linyuh183cb712017-12-27 17:02:37 -0800922 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800923 LogUtil.e("VideoCallPresenter.onUpdatePeerDimensions", "videoCallScreen is null");
924 return;
925 }
linyuh183cb712017-12-27 17:02:37 -0800926 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800927 LogUtil.e(
928 "VideoCallPresenter.onUpdatePeerDimensions", "current call is not equal to primary");
929 return;
930 }
931
932 // Change size of display surface to match the peer aspect ratio
linyuh183cb712017-12-27 17:02:37 -0800933 if (width > 0 && height > 0 && videoCallScreen != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800934 getRemoteVideoSurfaceTexture().setSourceVideoDimensions(new Point(width, height));
linyuh183cb712017-12-27 17:02:37 -0800935 videoCallScreen.onRemoteVideoDimensionsChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800936 }
937 }
938
939 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800940 * Handles a change to the dimensions of the local camera. Receiving the camera capabilities
941 * triggers the creation of the video
942 *
943 * @param call The call which experienced the camera dimension change.
944 * @param width The new camera video width.
945 * @param height The new camera video height.
946 */
947 @Override
948 public void onCameraDimensionsChange(DialerCall call, int width, int height) {
949 LogUtil.i(
950 "VideoCallPresenter.onCameraDimensionsChange",
951 "call: %s, width: %d, height: %d",
952 call,
953 width,
954 height);
linyuh183cb712017-12-27 17:02:37 -0800955 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800956 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "ui is null");
957 return;
958 }
959
linyuh183cb712017-12-27 17:02:37 -0800960 if (!call.equals(primaryCall)) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800961 LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "not the primary call");
962 return;
963 }
964
linyuh183cb712017-12-27 17:02:37 -0800965 previewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
Eric Erfanianccca3152017-02-22 16:32:36 -0800966 changePreviewDimensions(width, height);
967
968 // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
969 // If it not yet ready, it will be set when when creation completes.
970 Surface surface = getLocalVideoSurfaceTexture().getSavedSurface();
971 if (surface != null) {
linyuh183cb712017-12-27 17:02:37 -0800972 previewSurfaceState = PreviewSurfaceState.SURFACE_SET;
973 videoCall.setPreviewSurface(surface);
Eric Erfanianccca3152017-02-22 16:32:36 -0800974 }
975 }
976
977 /**
978 * Changes the dimensions of the preview surface.
979 *
980 * @param width The new width.
981 * @param height The new height.
982 */
983 private void changePreviewDimensions(int width, int height) {
linyuh183cb712017-12-27 17:02:37 -0800984 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800985 return;
986 }
987
988 // Resize the surface used to display the preview video
989 getLocalVideoSurfaceTexture().setSurfaceDimensions(new Point(width, height));
linyuh183cb712017-12-27 17:02:37 -0800990 videoCallScreen.onLocalVideoDimensionsChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -0800991 }
992
993 /**
Eric Erfanianccca3152017-02-22 16:32:36 -0800994 * Handles changes to the device orientation.
995 *
996 * @param orientation The screen orientation of the device (one of: {@link
997 * InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
998 * InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
999 * InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
1000 * InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
1001 */
1002 @Override
1003 public void onDeviceOrientationChanged(int orientation) {
1004 LogUtil.i(
1005 "VideoCallPresenter.onDeviceOrientationChanged",
1006 "orientation: %d -> %d",
linyuh183cb712017-12-27 17:02:37 -08001007 deviceOrientation,
Eric Erfanianccca3152017-02-22 16:32:36 -08001008 orientation);
linyuh183cb712017-12-27 17:02:37 -08001009 deviceOrientation = orientation;
Eric Erfanianccca3152017-02-22 16:32:36 -08001010
linyuh183cb712017-12-27 17:02:37 -08001011 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001012 LogUtil.e("VideoCallPresenter.onDeviceOrientationChanged", "videoCallScreen is null");
1013 return;
1014 }
1015
1016 Point previewDimensions = getLocalVideoSurfaceTexture().getSurfaceDimensions();
1017 if (previewDimensions == null) {
1018 return;
1019 }
1020 LogUtil.v(
1021 "VideoCallPresenter.onDeviceOrientationChanged",
1022 "orientation: %d, size: %s",
1023 orientation,
1024 previewDimensions);
1025 changePreviewDimensions(previewDimensions.x, previewDimensions.y);
1026
linyuh183cb712017-12-27 17:02:37 -08001027 videoCallScreen.onLocalVideoOrientationChanged();
Eric Erfanianccca3152017-02-22 16:32:36 -08001028 }
1029
1030 /**
1031 * Exits fullscreen mode if the current call context has changed to a non-video call.
1032 *
1033 * @param call The call.
1034 */
1035 protected void maybeExitFullscreen(DialerCall call) {
1036 if (call == null) {
1037 return;
1038 }
1039
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001040 if (!isVideoCall(call) || call.getState() == DialerCall.State.INCOMING) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001041 LogUtil.i("VideoCallPresenter.maybeExitFullscreen", "exiting fullscreen");
1042 InCallPresenter.getInstance().setFullScreen(false);
1043 }
1044 }
1045
1046 /**
1047 * Schedules auto-entering of fullscreen mode. Will not enter full screen mode if any of the
1048 * following conditions are met: 1. No call 2. DialerCall is not active 3. The current video state
1049 * is not bi-directional. 4. Already in fullscreen mode 5. In accessibility mode
1050 *
1051 * @param call The current call.
1052 */
1053 protected void maybeAutoEnterFullscreen(DialerCall call) {
linyuh183cb712017-12-27 17:02:37 -08001054 if (!isAutoFullscreenEnabled) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001055 return;
1056 }
1057
1058 if (call == null
1059 || call.getState() != DialerCall.State.ACTIVE
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001060 || !isBidirectionalVideoCall(call)
Eric Erfanianccca3152017-02-22 16:32:36 -08001061 || InCallPresenter.getInstance().isFullscreen()
linyuh183cb712017-12-27 17:02:37 -08001062 || (context != null && AccessibilityUtil.isTouchExplorationEnabled(context))) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001063 // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
1064 cancelAutoFullScreen();
1065 return;
1066 }
1067
linyuh183cb712017-12-27 17:02:37 -08001068 if (autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001069 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "already pending.");
1070 return;
1071 }
1072 LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "scheduled");
linyuh183cb712017-12-27 17:02:37 -08001073 autoFullScreenPending = true;
1074 handler.removeCallbacks(autoFullscreenRunnable);
1075 handler.postDelayed(autoFullscreenRunnable, autoFullscreenTimeoutMillis);
Eric Erfanianccca3152017-02-22 16:32:36 -08001076 }
1077
1078 /** Cancels pending auto fullscreen mode. */
1079 @Override
1080 public void cancelAutoFullScreen() {
linyuh183cb712017-12-27 17:02:37 -08001081 if (!autoFullScreenPending) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001082 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "none pending.");
1083 return;
1084 }
1085 LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "cancelling pending");
linyuh183cb712017-12-27 17:02:37 -08001086 autoFullScreenPending = false;
1087 handler.removeCallbacks(autoFullscreenRunnable);
Eric Erfanianccca3152017-02-22 16:32:36 -08001088 }
1089
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001090 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001091 public boolean shouldShowCameraPermissionToast() {
linyuh183cb712017-12-27 17:02:37 -08001092 if (primaryCall == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001093 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "null call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001094 return false;
1095 }
linyuh183cb712017-12-27 17:02:37 -08001096 if (primaryCall.didShowCameraPermission()) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001097 LogUtil.i(
Eric Erfanian2ca43182017-08-31 06:57:16 -07001098 "VideoCallPresenter.shouldShowCameraPermissionToast", "already shown for this call");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001099 return false;
1100 }
linyuh183cb712017-12-27 17:02:37 -08001101 if (!ConfigProviderBindings.get(context).getBoolean("camera_permission_dialog_allowed", true)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001102 LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionToast", "disabled by config");
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001103 return false;
1104 }
linyuh183cb712017-12-27 17:02:37 -08001105 return !VideoUtils.hasCameraPermission(context)
1106 || !PermissionsUtil.hasCameraPrivacyToastShown(context);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001107 }
1108
1109 @Override
1110 public void onCameraPermissionDialogShown() {
linyuh183cb712017-12-27 17:02:37 -08001111 if (primaryCall != null) {
1112 primaryCall.setDidShowCameraPermission(true);
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001113 }
1114 }
1115
Eric Erfanianccca3152017-02-22 16:32:36 -08001116 private void updateRemoteVideoSurfaceDimensions() {
linyuh183cb712017-12-27 17:02:37 -08001117 Activity activity = videoCallScreen.getVideoCallScreenFragment().getActivity();
Eric Erfanianccca3152017-02-22 16:32:36 -08001118 if (activity != null) {
1119 Point screenSize = new Point();
1120 activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
1121 getRemoteVideoSurfaceTexture().setSurfaceDimensions(screenSize);
1122 }
1123 }
1124
1125 private static boolean isVideoUpgrade(DialerCall call) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001126 return call != null
1127 && (call.hasSentVideoUpgradeRequest() || call.hasReceivedVideoUpgradeRequest());
Eric Erfanianccca3152017-02-22 16:32:36 -08001128 }
1129
1130 private static boolean isVideoUpgrade(@SessionModificationState int state) {
1131 return VideoUtils.hasSentVideoUpgradeRequest(state)
1132 || VideoUtils.hasReceivedVideoUpgradeRequest(state);
1133 }
1134
wangqida410d32018-03-06 16:51:38 -08001135 @Override
1136 public void onIncomingCall(DialerCall call) {}
1137
1138 @Override
1139 public void onUpgradeToVideo(DialerCall call) {}
1140
1141 @Override
1142 public void onSessionModificationStateChange(DialerCall call) {}
1143
1144 @Override
1145 public void onCallListChange(CallList callList) {}
1146
1147 @Override
1148 public void onDisconnect(DialerCall call) {}
1149
1150 @Override
1151 public void onWiFiToLteHandover(DialerCall call) {
1152 if (call.isVideoCall() || call.hasSentVideoUpgradeRequest()) {
1153 videoCallScreen.onHandoverFromWiFiToLte();
1154 }
1155 }
1156
1157 @Override
1158 public void onHandoverToWifiFailed(DialerCall call) {}
1159
1160 @Override
1161 public void onInternationalCallOnWifi(@NonNull DialerCall call) {}
1162
Eric Erfanianccca3152017-02-22 16:32:36 -08001163 private class LocalDelegate implements VideoSurfaceDelegate {
1164 @Override
1165 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001166 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001167 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no UI");
1168 return;
1169 }
linyuh183cb712017-12-27 17:02:37 -08001170 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001171 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no video call");
1172 return;
1173 }
1174
1175 // If the preview surface has just been created and we have already received camera
1176 // capabilities, but not yet set the surface, we will set the surface now.
linyuh183cb712017-12-27 17:02:37 -08001177 if (previewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
1178 previewSurfaceState = PreviewSurfaceState.SURFACE_SET;
1179 videoCall.setPreviewSurface(videoCallSurface.getSavedSurface());
1180 } else if (previewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
1181 enableCamera(primaryCall, true);
Eric Erfanianccca3152017-02-22 16:32:36 -08001182 }
1183 }
1184
1185 @Override
1186 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001187 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001188 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceReleased", "no video call");
1189 return;
1190 }
1191
linyuh183cb712017-12-27 17:02:37 -08001192 videoCall.setPreviewSurface(null);
1193 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -08001194 }
1195
1196 @Override
1197 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001198 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001199 LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", "no video call");
1200 return;
1201 }
1202
1203 boolean isChangingConfigurations = InCallPresenter.getInstance().isChangingConfigurations();
1204 if (!isChangingConfigurations) {
linyuh183cb712017-12-27 17:02:37 -08001205 enableCamera(primaryCall, false);
Eric Erfanianccca3152017-02-22 16:32:36 -08001206 } else {
1207 LogUtil.i(
1208 "VideoCallPresenter.LocalDelegate.onSurfaceDestroyed",
1209 "activity is being destroyed due to configuration changes. Not closing the camera.");
1210 }
1211 }
1212
1213 @Override
1214 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1215 VideoCallPresenter.this.onSurfaceClick();
1216 }
1217 }
1218
1219 private class RemoteDelegate implements VideoSurfaceDelegate {
1220 @Override
1221 public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001222 if (videoCallScreen == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001223 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no UI");
1224 return;
1225 }
linyuh183cb712017-12-27 17:02:37 -08001226 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001227 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no video call");
1228 return;
1229 }
linyuh183cb712017-12-27 17:02:37 -08001230 videoCall.setDisplaySurface(videoCallSurface.getSavedSurface());
Eric Erfanianccca3152017-02-22 16:32:36 -08001231 }
1232
1233 @Override
1234 public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
linyuh183cb712017-12-27 17:02:37 -08001235 if (videoCall == null) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001236 LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceReleased", "no video call");
1237 return;
1238 }
linyuh183cb712017-12-27 17:02:37 -08001239 videoCall.setDisplaySurface(null);
Eric Erfanianccca3152017-02-22 16:32:36 -08001240 }
1241
1242 @Override
1243 public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {}
1244
1245 @Override
1246 public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
1247 VideoCallPresenter.this.onSurfaceClick();
1248 }
1249 }
1250
1251 /** Defines the state of the preview surface negotiation with the telephony layer. */
1252 private static class PreviewSurfaceState {
1253
1254 /**
1255 * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet started.
1256 */
1257 private static final int NONE = 0;
1258
1259 /**
1260 * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet been
1261 * received.
1262 */
1263 private static final int CAMERA_SET = 1;
1264
1265 /**
1266 * The camera capabilties have been received from telephony, but the surface has not yet been
1267 * set on the {@link VideoCall}.
1268 */
1269 private static final int CAPABILITIES_RECEIVED = 2;
1270
1271 /** The surface has been set on the {@link VideoCall}. */
1272 private static final int SURFACE_SET = 3;
1273 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001274
1275 private static boolean isBidirectionalVideoCall(DialerCall call) {
1276 return CompatUtils.isVideoCompatible() && VideoProfile.isBidirectional(call.getVideoState());
1277 }
1278
1279 private static boolean isIncomingVideoCall(DialerCall call) {
1280 if (!isVideoCall(call)) {
1281 return false;
1282 }
1283 final int state = call.getState();
1284 return (state == DialerCall.State.INCOMING) || (state == DialerCall.State.CALL_WAITING);
1285 }
1286
1287 private static boolean isActiveVideoCall(DialerCall call) {
1288 return isVideoCall(call) && call.getState() == DialerCall.State.ACTIVE;
1289 }
1290
1291 private static boolean isOutgoingVideoCall(DialerCall call) {
1292 if (!isVideoCall(call)) {
1293 return false;
1294 }
1295 final int state = call.getState();
1296 return DialerCall.State.isDialing(state)
1297 || state == DialerCall.State.CONNECTING
1298 || state == DialerCall.State.SELECT_PHONE_ACCOUNT;
1299 }
1300
1301 private static boolean isAudioCall(DialerCall call) {
1302 if (!CompatUtils.isVideoCompatible()) {
1303 return true;
1304 }
1305
1306 return call != null && VideoProfile.isAudioOnly(call.getVideoState());
1307 }
1308
1309 private static boolean isVideoCall(@Nullable DialerCall call) {
1310 return call != null && call.isVideoCall();
1311 }
1312
1313 private static boolean isVideoCall(int videoState) {
1314 return CompatUtils.isVideoCompatible()
1315 && (VideoProfile.isTransmissionEnabled(videoState)
1316 || VideoProfile.isReceptionEnabled(videoState));
1317 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001318}