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