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