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