Update dialer sources.

Test: Built package and system image.

This change clobbers the old source, and is an export
from an internal Google repository.

The internal repository was forked form Android in March,
and this change includes modifications since then, to
near the v8 release.

Since the fork, we've moved code from monolithic to independent modules. In addition,
we've switched to Blaze/Bazel as the build sysetm. This export, however, still uses make.

New dependencies have been added:
- Dagger
- Auto-Value
- Glide
- Libshortcutbadger

Going forward, development will still be in Google3, and the Gerrit release
will become an automated export, with the next drop happening in ~ two weeks.

Android.mk includes local modifications from ToT.

Abridged changelog:

Bug fixes
● Not able to mute, add a call when using Phone app in multiwindow mode
● Double tap on keypad triggering multiple key and tones
● Reported spam numbers not showing as spam in the call log
● Crash when user tries to block number while Phone app is not set as default
● Crash when user picks a number from search auto-complete list

Visual Voicemail (VVM) improvements
● Share Voicemail audio via standard exporting mechanisms that support file attachment
(email, MMS, etc.)
● Make phone number, email and web sites in VVM transcript clickable
● Set PIN before declining VVM Terms of Service {Carrier}
● Set client type for outbound visual voicemail SMS {Carrier}

New incoming call and incall UI on older devices
(Android M)
● Updated Phone app icon
● New incall UI (large buttons, button labels)
● New and animated Answer/Reject gestures

Accessibility
● Add custom answer/decline call buttons on answer screen for touch exploration
accessibility services
● Increase size of touch target
● Add verbal feedback when a Voicemail fails to load
● Fix pressing of Phone buttons while in a phone call using Switch Access
● Fix selecting and opening contacts in talkback mode
● Split focus for ‘Learn More’ link in caller id & spam to help distinguish similar text

Other
● Backup & Restore for App Preferences
● Prompt user to enable Wi-Fi calling if the call ends due to out of service and Wi-Fi is
connected
● Rename “Dialpad” to “Keypad”
● Show "Private number" for restricted calls
● Delete unused items (vcard, add contact, call history) from Phone menu

Change-Id: I2a7e53532a24c21bf308bf0a6d178d7ddbca4958
diff --git a/java/com/android/incallui/VideoCallPresenter.java b/java/com/android/incallui/VideoCallPresenter.java
new file mode 100644
index 0000000..971b695
--- /dev/null
+++ b/java/com/android/incallui/VideoCallPresenter.java
@@ -0,0 +1,1289 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.telecom.Connection;
+import android.telecom.InCallService.VideoCall;
+import android.telecom.VideoProfile;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.view.Surface;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.ConfigProviderBindings;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.compat.CompatUtils;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.android.incallui.InCallPresenter.InCallOrientationListener;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall;
+import com.android.incallui.call.DialerCall.SessionModificationState;
+import com.android.incallui.call.DialerCall.State;
+import com.android.incallui.call.InCallVideoCallCallbackNotifier;
+import com.android.incallui.call.InCallVideoCallCallbackNotifier.SurfaceChangeListener;
+import com.android.incallui.call.InCallVideoCallCallbackNotifier.VideoEventListener;
+import com.android.incallui.call.VideoUtils;
+import com.android.incallui.util.AccessibilityUtil;
+import com.android.incallui.video.protocol.VideoCallScreen;
+import com.android.incallui.video.protocol.VideoCallScreenDelegate;
+import com.android.incallui.videosurface.protocol.VideoSurfaceDelegate;
+import com.android.incallui.videosurface.protocol.VideoSurfaceTexture;
+import java.util.Objects;
+
+/**
+ * Logic related to the {@link VideoCallScreen} and for managing changes to the video calling
+ * surfaces based on other user interface events and incoming events from the {@class
+ * VideoCallListener}.
+ *
+ * <p>When a call's video state changes to bi-directional video, the {@link
+ * com.android.incallui.VideoCallPresenter} performs the following negotiation with the telephony
+ * layer:
+ *
+ * <ul>
+ * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.
+ * <li>{@code VideoCallPresenter} creates the preview surface.
+ * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.
+ * <li>Telephony layer sends {@link CameraCapabilities}, including the dimensions of the video for
+ *     the current camera.
+ * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect ratio of
+ *     the camera.
+ * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.
+ * </ul>
+ *
+ * <p>When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both
+ * surfaces.
+ */
+public class VideoCallPresenter
+    implements IncomingCallListener,
+        InCallOrientationListener,
+        InCallStateListener,
+        InCallDetailsListener,
+        SurfaceChangeListener,
+        VideoEventListener,
+        InCallPresenter.InCallEventListener,
+        VideoCallScreenDelegate {
+
+  private static boolean mIsVideoMode = false;
+
+  private final Handler mHandler = new Handler();
+  private VideoCallScreen mVideoCallScreen;
+
+  /** The current context. */
+  private Context mContext;
+
+  @Override
+  public boolean shouldShowCameraPermissionDialog() {
+    if (mPrimaryCall == null) {
+      LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionDialog", "null call");
+      return false;
+    }
+    if (mPrimaryCall.didShowCameraPermission()) {
+      LogUtil.i(
+          "VideoCallPresenter.shouldShowCameraPermissionDialog", "already shown for this call");
+      return false;
+    }
+    if (!ConfigProviderBindings.get(mContext)
+        .getBoolean("camera_permission_dialog_allowed", true)) {
+      LogUtil.i("VideoCallPresenter.shouldShowCameraPermissionDialog", "disabled by config");
+      return false;
+    }
+    return !VideoUtils.hasCameraPermission(mContext) || !VideoUtils.isCameraAllowedByUser(mContext);
+  }
+
+  @Override
+  public void onCameraPermissionDialogShown() {
+    if (mPrimaryCall != null) {
+      mPrimaryCall.setDidShowCameraPermission(true);
+    }
+  }
+
+  /** The call the video surfaces are currently related to */
+  private DialerCall mPrimaryCall;
+  /**
+   * The {@link VideoCall} used to inform the video telephony layer of changes to the video
+   * surfaces.
+   */
+  private VideoCall mVideoCall;
+  /** Determines if the current UI state represents a video call. */
+  private int mCurrentVideoState;
+  /** DialerCall's current state */
+  private int mCurrentCallState = DialerCall.State.INVALID;
+  /** Determines the device orientation (portrait/lanscape). */
+  private int mDeviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN;
+  /** Tracks the state of the preview surface negotiation with the telephony layer. */
+  private int mPreviewSurfaceState = PreviewSurfaceState.NONE;
+  /**
+   * Determines whether video calls should automatically enter full screen mode after {@link
+   * #mAutoFullscreenTimeoutMillis} milliseconds.
+   */
+  private boolean mIsAutoFullscreenEnabled = false;
+  /**
+   * Determines the number of milliseconds after which a video call will automatically enter
+   * fullscreen mode. Requires {@link #mIsAutoFullscreenEnabled} to be {@code true}.
+   */
+  private int mAutoFullscreenTimeoutMillis = 0;
+  /**
+   * Determines if the countdown is currently running to automatically enter full screen video mode.
+   */
+  private boolean mAutoFullScreenPending = false;
+  /** Whether if the call is remotely held. */
+  private boolean mIsRemotelyHeld = false;
+  /**
+   * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto
+   * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit the
+   * dialpad).
+   */
+  private Runnable mAutoFullscreenRunnable =
+      new Runnable() {
+        @Override
+        public void run() {
+          if (mAutoFullScreenPending
+              && !InCallPresenter.getInstance().isDialpadVisible()
+              && mIsVideoMode) {
+
+            LogUtil.v("VideoCallPresenter.mAutoFullScreenRunnable", "entering fullscreen mode");
+            InCallPresenter.getInstance().setFullScreen(true);
+            mAutoFullScreenPending = false;
+          } else {
+            LogUtil.v(
+                "VideoCallPresenter.mAutoFullScreenRunnable",
+                "skipping scheduled fullscreen mode.");
+          }
+        }
+      };
+
+  private boolean isVideoCallScreenUiReady;
+
+  private static boolean isCameraRequired(int videoState, int sessionModificationState) {
+    return VideoProfile.isBidirectional(videoState)
+        || VideoProfile.isTransmissionEnabled(videoState)
+        || isVideoUpgrade(sessionModificationState);
+  }
+
+  /**
+   * Determines if the incoming video surface should be shown based on the current videoState and
+   * callState. The video surface is shown when incoming video is not paused, the call is active,
+   * and video reception is enabled.
+   *
+   * @param videoState The current video state.
+   * @param callState The current call state.
+   * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise.
+   */
+  public static boolean showIncomingVideo(int videoState, int callState) {
+    if (!CompatUtils.isVideoCompatible()) {
+      return false;
+    }
+
+    boolean isPaused = VideoProfile.isPaused(videoState);
+    boolean isCallActive = callState == DialerCall.State.ACTIVE;
+
+    return !isPaused && isCallActive && VideoProfile.isReceptionEnabled(videoState);
+  }
+
+  /**
+   * Determines if the outgoing video surface should be shown based on the current videoState. The
+   * video surface is shown if video transmission is enabled.
+   *
+   * @return {@code true} if the the outgoing video surface should be shown, {@code false}
+   *     otherwise.
+   */
+  public static boolean showOutgoingVideo(
+      Context context, int videoState, int sessionModificationState) {
+    if (!VideoUtils.hasCameraPermissionAndAllowedByUser(context)) {
+      LogUtil.i("VideoCallPresenter.showOutgoingVideo", "Camera permission is disabled by user.");
+      return false;
+    }
+
+    if (!CompatUtils.isVideoCompatible()) {
+      return false;
+    }
+
+    return VideoProfile.isTransmissionEnabled(videoState)
+        || isVideoUpgrade(sessionModificationState);
+  }
+
+  private static void updateCameraSelection(DialerCall call) {
+    LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + call);
+    LogUtil.v("VideoCallPresenter.updateCameraSelection", "call=" + toSimpleString(call));
+
+    final DialerCall activeCall = CallList.getInstance().getActiveCall();
+    int cameraDir;
+
+    // this function should never be called with null call object, however if it happens we
+    // should handle it gracefully.
+    if (call == null) {
+      cameraDir = DialerCall.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+      LogUtil.e(
+          "VideoCallPresenter.updateCameraSelection",
+          "call is null. Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
+    }
+
+    // Clear camera direction if this is not a video call.
+    else if (VideoUtils.isAudioCall(call) && !isVideoUpgrade(call)) {
+      cameraDir = DialerCall.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+      call.getVideoSettings().setCameraDir(cameraDir);
+    }
+
+    // If this is a waiting video call, default to active call's camera,
+    // since we don't want to change the current camera for waiting call
+    // without user's permission.
+    else if (VideoUtils.isVideoCall(activeCall) && VideoUtils.isIncomingVideoCall(call)) {
+      cameraDir = activeCall.getVideoSettings().getCameraDir();
+    }
+
+    // Infer the camera direction from the video state and store it,
+    // if this is an outgoing video call.
+    else if (VideoUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call)) {
+      cameraDir = toCameraDirection(call.getVideoState());
+      call.getVideoSettings().setCameraDir(cameraDir);
+    }
+
+    // Use the stored camera dir if this is an outgoing video call for which camera direction
+    // is set.
+    else if (VideoUtils.isOutgoingVideoCall(call)) {
+      cameraDir = call.getVideoSettings().getCameraDir();
+    }
+
+    // Infer the camera direction from the video state and store it,
+    // if this is an active video call and camera direction is not set.
+    else if (VideoUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
+      cameraDir = toCameraDirection(call.getVideoState());
+      call.getVideoSettings().setCameraDir(cameraDir);
+    }
+
+    // Use the stored camera dir if this is an active video call for which camera direction
+    // is set.
+    else if (VideoUtils.isActiveVideoCall(call)) {
+      cameraDir = call.getVideoSettings().getCameraDir();
+    }
+
+    // For all other cases infer the camera direction but don't store it in the call object.
+    else {
+      cameraDir = toCameraDirection(call.getVideoState());
+    }
+
+    LogUtil.i(
+        "VideoCallPresenter.updateCameraSelection",
+        "setting camera direction to %d, call: %s",
+        cameraDir,
+        call);
+    final InCallCameraManager cameraManager =
+        InCallPresenter.getInstance().getInCallCameraManager();
+    cameraManager.setUseFrontFacingCamera(
+        cameraDir == DialerCall.VideoSettings.CAMERA_DIRECTION_FRONT_FACING);
+  }
+
+  private static int toCameraDirection(int videoState) {
+    return VideoProfile.isTransmissionEnabled(videoState)
+            && !VideoProfile.isBidirectional(videoState)
+        ? DialerCall.VideoSettings.CAMERA_DIRECTION_BACK_FACING
+        : DialerCall.VideoSettings.CAMERA_DIRECTION_FRONT_FACING;
+  }
+
+  private static boolean isCameraDirectionSet(DialerCall call) {
+    return VideoUtils.isVideoCall(call)
+        && call.getVideoSettings().getCameraDir()
+            != DialerCall.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+  }
+
+  private static String toSimpleString(DialerCall call) {
+    return call == null ? null : call.toSimpleString();
+  }
+
+  /**
+   * Initializes the presenter.
+   *
+   * @param context The current context.
+   */
+  @Override
+  public void initVideoCallScreenDelegate(Context context, VideoCallScreen videoCallScreen) {
+    mContext = context;
+    mVideoCallScreen = videoCallScreen;
+    mIsAutoFullscreenEnabled =
+        mContext.getResources().getBoolean(R.bool.video_call_auto_fullscreen);
+    mAutoFullscreenTimeoutMillis =
+        mContext.getResources().getInteger(R.integer.video_call_auto_fullscreen_timeout);
+  }
+
+  /** Called when the user interface is ready to be used. */
+  @Override
+  public void onVideoCallScreenUiReady() {
+    LogUtil.v("VideoCallPresenter.onVideoCallScreenUiReady", "");
+    Assert.checkState(!isVideoCallScreenUiReady);
+
+    // Do not register any listeners if video calling is not compatible to safeguard against
+    // any accidental calls of video calling code.
+    if (!CompatUtils.isVideoCompatible()) {
+      return;
+    }
+
+    mDeviceOrientation = InCallOrientationEventListener.getCurrentOrientation();
+
+    // Register for call state changes last
+    InCallPresenter.getInstance().addListener(this);
+    InCallPresenter.getInstance().addDetailsListener(this);
+    InCallPresenter.getInstance().addIncomingCallListener(this);
+    InCallPresenter.getInstance().addOrientationListener(this);
+    // To get updates of video call details changes
+    InCallPresenter.getInstance().addInCallEventListener(this);
+    InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(new LocalDelegate());
+    InCallPresenter.getInstance().getRemoteVideoSurfaceTexture().setDelegate(new RemoteDelegate());
+
+    // Register for surface and video events from {@link InCallVideoCallListener}s.
+    InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
+    InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this);
+    mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
+    mCurrentCallState = DialerCall.State.INVALID;
+
+    InCallPresenter.InCallState inCallState = InCallPresenter.getInstance().getInCallState();
+    onStateChange(inCallState, inCallState, CallList.getInstance());
+    isVideoCallScreenUiReady = true;
+  }
+
+  /** Called when the user interface is no longer ready to be used. */
+  @Override
+  public void onVideoCallScreenUiUnready() {
+    LogUtil.v("VideoCallPresenter.onVideoCallScreenUiUnready", "");
+    Assert.checkState(isVideoCallScreenUiReady);
+
+    if (!CompatUtils.isVideoCompatible()) {
+      return;
+    }
+
+    cancelAutoFullScreen();
+
+    InCallPresenter.getInstance().removeListener(this);
+    InCallPresenter.getInstance().removeDetailsListener(this);
+    InCallPresenter.getInstance().removeIncomingCallListener(this);
+    InCallPresenter.getInstance().removeOrientationListener(this);
+    InCallPresenter.getInstance().removeInCallEventListener(this);
+    InCallPresenter.getInstance().getLocalVideoSurfaceTexture().setDelegate(null);
+
+    InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
+    InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
+
+    // Ensure that the call's camera direction is updated (most likely to UNKNOWN). Normally this
+    // happens after any call state changes but we're unregistering from InCallPresenter above so
+    // we won't get any more call state changes. See b/32957114.
+    if (mPrimaryCall != null) {
+      updateCameraSelection(mPrimaryCall);
+    }
+
+    isVideoCallScreenUiReady = false;
+  }
+
+  /**
+   * Handles clicks on the video surfaces. If not currently in fullscreen mode, will set fullscreen.
+   */
+  private void onSurfaceClick() {
+    LogUtil.i("VideoCallPresenter.onSurfaceClick", "");
+    cancelAutoFullScreen();
+    if (!InCallPresenter.getInstance().isFullscreen()) {
+      InCallPresenter.getInstance().setFullScreen(true);
+    } else {
+      InCallPresenter.getInstance().setFullScreen(false);
+      maybeAutoEnterFullscreen(mPrimaryCall);
+      // If Activity is not multiwindow, fullscreen will be driven by SystemUI visibility changes
+      // instead. See #onSystemUiVisibilityChange(boolean)
+
+      // TODO (keyboardr): onSystemUiVisibilityChange isn't being called the first time
+      // visibility changes after orientation change, so this is currently always done as a backup.
+    }
+  }
+
+  @Override
+  public void onSystemUiVisibilityChange(boolean visible) {
+    // If the SystemUI has changed to be visible, take us out of fullscreen mode
+    LogUtil.i("VideoCallPresenter.onSystemUiVisibilityChange", "visible: " + visible);
+    if (visible) {
+      InCallPresenter.getInstance().setFullScreen(false);
+      maybeAutoEnterFullscreen(mPrimaryCall);
+    }
+  }
+
+  @Override
+  public VideoSurfaceTexture getLocalVideoSurfaceTexture() {
+    return InCallPresenter.getInstance().getLocalVideoSurfaceTexture();
+  }
+
+  @Override
+  public VideoSurfaceTexture getRemoteVideoSurfaceTexture() {
+    return InCallPresenter.getInstance().getRemoteVideoSurfaceTexture();
+  }
+
+  @Override
+  public int getDeviceOrientation() {
+    return mDeviceOrientation;
+  }
+
+  /**
+   * This should only be called when user approved the camera permission, which is local action and
+   * does NOT change any call states.
+   */
+  @Override
+  public void onCameraPermissionGranted() {
+    LogUtil.i("VideoCallPresenter.onCameraPermissionGranted", "");
+    VideoUtils.setCameraAllowedByUser(mContext);
+    enableCamera(mPrimaryCall.getVideoCall(), isCameraRequired());
+    showVideoUi(
+        mPrimaryCall.getVideoState(),
+        mPrimaryCall.getState(),
+        mPrimaryCall.getSessionModificationState(),
+        mPrimaryCall.isRemotelyHeld());
+    InCallPresenter.getInstance().getInCallCameraManager().onCameraPermissionGranted();
+  }
+
+  /**
+   * Called when the user interacts with the UI. If a fullscreen timer is pending then we start the
+   * timer from scratch to avoid having the UI disappear while the user is interacting with it.
+   */
+  @Override
+  public void resetAutoFullscreenTimer() {
+    if (mAutoFullScreenPending) {
+      LogUtil.i("VideoCallPresenter.resetAutoFullscreenTimer", "resetting");
+      mHandler.removeCallbacks(mAutoFullscreenRunnable);
+      mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis);
+    }
+  }
+
+  /**
+   * Handles incoming calls.
+   *
+   * @param oldState The old in call state.
+   * @param newState The new in call state.
+   * @param call The call.
+   */
+  @Override
+  public void onIncomingCall(
+      InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, DialerCall call) {
+    // same logic should happen as with onStateChange()
+    onStateChange(oldState, newState, CallList.getInstance());
+  }
+
+  /**
+   * Handles state changes (including incoming calls)
+   *
+   * @param newState The in call state.
+   * @param callList The call list.
+   */
+  @Override
+  public void onStateChange(
+      InCallPresenter.InCallState oldState,
+      InCallPresenter.InCallState newState,
+      CallList callList) {
+    LogUtil.v(
+        "VideoCallPresenter.onStateChange",
+        "oldState: %s, newState: %s, isVideoMode: %b",
+        oldState,
+        newState,
+        isVideoMode());
+
+    if (newState == InCallPresenter.InCallState.NO_CALLS) {
+      if (isVideoMode()) {
+        exitVideoMode();
+      }
+
+      InCallPresenter.getInstance().cleanupSurfaces();
+    }
+
+    // Determine the primary active call).
+    DialerCall primary = null;
+
+    // Determine the call which is the focus of the user's attention.  In the case of an
+    // incoming call waiting call, the primary call is still the active video call, however
+    // the determination of whether we should be in fullscreen mode is based on the type of the
+    // incoming call, not the active video call.
+    DialerCall currentCall = null;
+
+    if (newState == InCallPresenter.InCallState.INCOMING) {
+      // We don't want to replace active video call (primary call)
+      // with a waiting call, since user may choose to ignore/decline the waiting call and
+      // this should have no impact on current active video call, that is, we should not
+      // change the camera or UI unless the waiting VT call becomes active.
+      primary = callList.getActiveCall();
+      currentCall = callList.getIncomingCall();
+      if (!VideoUtils.isActiveVideoCall(primary)) {
+        primary = callList.getIncomingCall();
+      }
+    } else if (newState == InCallPresenter.InCallState.OUTGOING) {
+      currentCall = primary = callList.getOutgoingCall();
+    } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
+      currentCall = primary = callList.getPendingOutgoingCall();
+    } else if (newState == InCallPresenter.InCallState.INCALL) {
+      currentCall = primary = callList.getActiveCall();
+    }
+
+    final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary);
+    LogUtil.i(
+        "VideoCallPresenter.onStateChange",
+        "primaryChanged: %b, primary: %s, mPrimaryCall: %s",
+        primaryChanged,
+        primary,
+        mPrimaryCall);
+    if (primaryChanged) {
+      onPrimaryCallChanged(primary);
+    } else if (mPrimaryCall != null) {
+      updateVideoCall(primary);
+    }
+    updateCallCache(primary);
+
+    // If the call context changed, potentially exit fullscreen or schedule auto enter of
+    // fullscreen mode.
+    // If the current call context is no longer a video call, exit fullscreen mode.
+    maybeExitFullscreen(currentCall);
+    // Schedule auto-enter of fullscreen mode if the current call context is a video call
+    maybeAutoEnterFullscreen(currentCall);
+  }
+
+  /**
+   * Handles a change to the fullscreen mode of the app.
+   *
+   * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise.
+   */
+  @Override
+  public void onFullscreenModeChanged(boolean isFullscreenMode) {
+    cancelAutoFullScreen();
+    if (mPrimaryCall != null) {
+      updateFullscreenAndGreenScreenMode(
+          mPrimaryCall.getState(), mPrimaryCall.getSessionModificationState());
+    } else {
+      updateFullscreenAndGreenScreenMode(
+          State.INVALID, DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST);
+    }
+  }
+
+  private void checkForVideoStateChange(DialerCall call) {
+    final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
+    final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
+
+    LogUtil.v(
+        "VideoCallPresenter.checkForVideoStateChange",
+        "shouldShowVideoUi: %b, hasVideoStateChanged: %b, isVideoMode: %b, previousVideoState: %s,"
+            + " newVideoState: %s",
+        shouldShowVideoUi,
+        hasVideoStateChanged,
+        isVideoMode(),
+        VideoProfile.videoStateToString(mCurrentVideoState),
+        VideoProfile.videoStateToString(call.getVideoState()));
+    if (!hasVideoStateChanged) {
+      return;
+    }
+
+    updateCameraSelection(call);
+
+    if (shouldShowVideoUi) {
+      adjustVideoMode(call);
+    } else if (isVideoMode()) {
+      exitVideoMode();
+    }
+  }
+
+  private void checkForCallStateChange(DialerCall call) {
+    final boolean shouldShowVideoUi = shouldShowVideoUiForCall(call);
+    final boolean hasCallStateChanged =
+        mCurrentCallState != call.getState() || mIsRemotelyHeld != call.isRemotelyHeld();
+    mIsRemotelyHeld = call.isRemotelyHeld();
+
+    LogUtil.v(
+        "VideoCallPresenter.checkForCallStateChange",
+        "shouldShowVideoUi: %b, hasCallStateChanged: %b, isVideoMode: %b",
+        shouldShowVideoUi,
+        hasCallStateChanged,
+        isVideoMode());
+
+    if (!hasCallStateChanged) {
+      return;
+    }
+
+    if (shouldShowVideoUi) {
+      final InCallCameraManager cameraManager =
+          InCallPresenter.getInstance().getInCallCameraManager();
+
+      String prevCameraId = cameraManager.getActiveCameraId();
+      updateCameraSelection(call);
+      String newCameraId = cameraManager.getActiveCameraId();
+
+      if (!Objects.equals(prevCameraId, newCameraId) && VideoUtils.isActiveVideoCall(call)) {
+        enableCamera(call.getVideoCall(), true);
+      }
+    }
+
+    // Make sure we hide or show the video UI if needed.
+    showVideoUi(
+        call.getVideoState(),
+        call.getState(),
+        call.getSessionModificationState(),
+        call.isRemotelyHeld());
+  }
+
+  private void onPrimaryCallChanged(DialerCall newPrimaryCall) {
+    final boolean shouldShowVideoUi = shouldShowVideoUiForCall(newPrimaryCall);
+    final boolean isVideoMode = isVideoMode();
+
+    LogUtil.v(
+        "VideoCallPresenter.onPrimaryCallChanged",
+        "shouldShowVideoUi: %b, isVideoMode: %b",
+        shouldShowVideoUi,
+        isVideoMode);
+
+    if (!shouldShowVideoUi && isVideoMode) {
+      // Terminate video mode if new primary call is not a video call
+      // and we are currently in video mode.
+      LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "exiting video mode...");
+      exitVideoMode();
+    } else if (shouldShowVideoUi) {
+      LogUtil.i("VideoCallPresenter.onPrimaryCallChanged", "entering video mode...");
+
+      updateCameraSelection(newPrimaryCall);
+      adjustVideoMode(newPrimaryCall);
+    }
+    checkForOrientationAllowedChange(newPrimaryCall);
+  }
+
+  private boolean isVideoMode() {
+    return mIsVideoMode;
+  }
+
+  private void updateCallCache(DialerCall call) {
+    if (call == null) {
+      mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY;
+      mCurrentCallState = DialerCall.State.INVALID;
+      mVideoCall = null;
+      mPrimaryCall = null;
+    } else {
+      mCurrentVideoState = call.getVideoState();
+      mVideoCall = call.getVideoCall();
+      mCurrentCallState = call.getState();
+      mPrimaryCall = call;
+    }
+  }
+
+  /**
+   * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in
+   * changes to the video state.
+   *
+   * @param call The call for which the details changed.
+   * @param details The new call details.
+   */
+  @Override
+  public void onDetailsChanged(DialerCall call, android.telecom.Call.Details details) {
+    LogUtil.v(
+        "VideoCallPresenter.onDetailsChanged",
+        "call: %s, details: %s, mPrimaryCall: %s",
+        call,
+        details,
+        mPrimaryCall);
+    if (call == null) {
+      return;
+    }
+    // If the details change is not for the currently active call no update is required.
+    if (!call.equals(mPrimaryCall)) {
+      LogUtil.v("VideoCallPresenter.onDetailsChanged", "details not for current active call");
+      return;
+    }
+
+    updateVideoCall(call);
+
+    updateCallCache(call);
+  }
+
+  private void updateVideoCall(DialerCall call) {
+    checkForVideoCallChange(call);
+    checkForVideoStateChange(call);
+    checkForCallStateChange(call);
+    checkForOrientationAllowedChange(call);
+    updateFullscreenAndGreenScreenMode(call.getState(), call.getSessionModificationState());
+  }
+
+  private void checkForOrientationAllowedChange(@Nullable DialerCall call) {
+    InCallPresenter.getInstance()
+        .setInCallAllowsOrientationChange(VideoUtils.isVideoCall(call) || isVideoUpgrade(call));
+  }
+
+  private void updateFullscreenAndGreenScreenMode(
+      int callState, @SessionModificationState int sessionModificationState) {
+    if (mVideoCallScreen != null) {
+      boolean shouldShowFullscreen = InCallPresenter.getInstance().isFullscreen();
+      boolean shouldShowGreenScreen =
+          callState == State.DIALING
+              || callState == State.CONNECTING
+              || callState == State.INCOMING
+              || isVideoUpgrade(sessionModificationState);
+      mVideoCallScreen.updateFullscreenAndGreenScreenMode(
+          shouldShowFullscreen, shouldShowGreenScreen);
+    }
+  }
+
+  /** Checks for a change to the video call and changes it if required. */
+  private void checkForVideoCallChange(DialerCall call) {
+    final VideoCall videoCall = call.getVideoCall();
+    LogUtil.v(
+        "VideoCallPresenter.checkForVideoCallChange",
+        "videoCall: %s, mVideoCall: %s",
+        videoCall,
+        mVideoCall);
+    if (!Objects.equals(videoCall, mVideoCall)) {
+      changeVideoCall(call);
+    }
+  }
+
+  /**
+   * Handles a change to the video call. Sets the surfaces on the previous call to null and sets the
+   * surfaces on the new video call accordingly.
+   *
+   * @param call The new video call.
+   */
+  private void changeVideoCall(DialerCall call) {
+    final VideoCall videoCall = call == null ? null : call.getVideoCall();
+    LogUtil.i(
+        "VideoCallPresenter.changeVideoCall",
+        "videoCall: %s, mVideoCall: %s",
+        videoCall,
+        mVideoCall);
+    final boolean hasChanged = mVideoCall == null && videoCall != null;
+
+    mVideoCall = videoCall;
+    if (mVideoCall == null) {
+      LogUtil.v("VideoCallPresenter.changeVideoCall", "video call or primary call is null. Return");
+      return;
+    }
+
+    if (shouldShowVideoUiForCall(call) && hasChanged) {
+      adjustVideoMode(call);
+    }
+  }
+
+  private boolean isCameraRequired() {
+    return mPrimaryCall != null
+        && isCameraRequired(
+            mPrimaryCall.getVideoState(), mPrimaryCall.getSessionModificationState());
+  }
+
+  /**
+   * Adjusts the current video mode by setting up the preview and display surfaces as necessary.
+   * Expected to be called whenever the video state associated with a call changes (e.g. a user
+   * turns their camera on or off) to ensure the correct surfaces are shown/hidden. TODO: Need
+   * to adjust size and orientation of preview surface here.
+   */
+  private void adjustVideoMode(DialerCall call) {
+    VideoCall videoCall = call.getVideoCall();
+    int newVideoState = call.getVideoState();
+
+    LogUtil.i(
+        "VideoCallPresenter.adjustVideoMode",
+        "videoCall: %s, videoState: %d",
+        videoCall,
+        newVideoState);
+    if (mVideoCallScreen == null) {
+      LogUtil.e("VideoCallPresenter.adjustVideoMode", "error VideoCallScreen is null so returning");
+      return;
+    }
+
+    showVideoUi(
+        newVideoState, call.getState(), call.getSessionModificationState(), call.isRemotelyHeld());
+
+    // Communicate the current camera to telephony and make a request for the camera
+    // capabilities.
+    if (videoCall != null) {
+      Surface surface = getRemoteVideoSurfaceTexture().getSavedSurface();
+      if (surface != null) {
+        LogUtil.v(
+            "VideoCallPresenter.adjustVideoMode", "calling setDisplaySurface with: " + surface);
+        videoCall.setDisplaySurface(surface);
+      }
+
+      Assert.checkState(
+          mDeviceOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN);
+      videoCall.setDeviceOrientation(mDeviceOrientation);
+      enableCamera(videoCall, isCameraRequired(newVideoState, call.getSessionModificationState()));
+    }
+    int previousVideoState = mCurrentVideoState;
+    mCurrentVideoState = newVideoState;
+    mIsVideoMode = true;
+
+    // adjustVideoMode may be called if we are already in a 1-way video state.  In this case
+    // we do not want to trigger auto-fullscreen mode.
+    if (!VideoUtils.isVideoCall(previousVideoState) && VideoUtils.isVideoCall(newVideoState)) {
+      maybeAutoEnterFullscreen(call);
+    }
+  }
+
+  private static boolean shouldShowVideoUiForCall(@Nullable DialerCall call) {
+    if (call == null) {
+      return false;
+    }
+
+    if (VideoUtils.isVideoCall(call)) {
+      return true;
+    }
+
+    if (isVideoUpgrade(call)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  private void enableCamera(VideoCall videoCall, boolean isCameraRequired) {
+    LogUtil.v(
+        "VideoCallPresenter.enableCamera",
+        "videoCall: %s, enabling: %b",
+        videoCall,
+        isCameraRequired);
+    if (videoCall == null) {
+      LogUtil.i("VideoCallPresenter.enableCamera", "videoCall is null.");
+      return;
+    }
+
+    boolean hasCameraPermission = VideoUtils.hasCameraPermissionAndAllowedByUser(mContext);
+    if (!hasCameraPermission) {
+      videoCall.setCamera(null);
+      mPreviewSurfaceState = PreviewSurfaceState.NONE;
+      // TODO: Inform remote party that the video is off. This is similar to b/30256571.
+    } else if (isCameraRequired) {
+      InCallCameraManager cameraManager = InCallPresenter.getInstance().getInCallCameraManager();
+      videoCall.setCamera(cameraManager.getActiveCameraId());
+      mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
+      videoCall.requestCameraCapabilities();
+    } else {
+      mPreviewSurfaceState = PreviewSurfaceState.NONE;
+      videoCall.setCamera(null);
+    }
+  }
+
+  /** Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). */
+  private void exitVideoMode() {
+    LogUtil.i("VideoCallPresenter.exitVideoMode", "");
+
+    showVideoUi(
+        VideoProfile.STATE_AUDIO_ONLY,
+        DialerCall.State.ACTIVE,
+        DialerCall.SESSION_MODIFICATION_STATE_NO_REQUEST,
+        false /* isRemotelyHeld */);
+    enableCamera(mVideoCall, false);
+    InCallPresenter.getInstance().setFullScreen(false);
+
+    mIsVideoMode = false;
+  }
+
+  /**
+   * Based on the current video state and call state, show or hide the incoming and outgoing video
+   * surfaces. The outgoing video surface is shown any time video is transmitting. The incoming
+   * video surface is shown whenever the video is un-paused and active.
+   *
+   * @param videoState The video state.
+   * @param callState The call state.
+   */
+  private void showVideoUi(
+      int videoState,
+      int callState,
+      @SessionModificationState int sessionModificationState,
+      boolean isRemotelyHeld) {
+    if (mVideoCallScreen == null) {
+      LogUtil.e("VideoCallPresenter.showVideoUi", "videoCallScreen is null returning");
+      return;
+    }
+    boolean showIncomingVideo = showIncomingVideo(videoState, callState);
+    boolean showOutgoingVideo = showOutgoingVideo(mContext, videoState, sessionModificationState);
+    LogUtil.i(
+        "VideoCallPresenter.showVideoUi",
+        "showIncoming: %b, showOutgoing: %b, isRemotelyHeld: %b",
+        showIncomingVideo,
+        showOutgoingVideo,
+        isRemotelyHeld);
+    updateRemoteVideoSurfaceDimensions();
+    mVideoCallScreen.showVideoViews(showOutgoingVideo, showIncomingVideo, isRemotelyHeld);
+
+    InCallPresenter.getInstance().enableScreenTimeout(VideoProfile.isAudioOnly(videoState));
+    updateFullscreenAndGreenScreenMode(callState, sessionModificationState);
+  }
+
+  /**
+   * Handles peer video pause state changes.
+   *
+   * @param call The call which paused or un-pausedvideo transmission.
+   * @param paused {@code True} when the video transmission is paused, {@code false} when video
+   *     transmission resumes.
+   */
+  @Override
+  public void onPeerPauseStateChanged(DialerCall call, boolean paused) {
+    if (!call.equals(mPrimaryCall)) {
+      return;
+    }
+  }
+
+  /**
+   * Handles peer video dimension changes.
+   *
+   * @param call The call which experienced a peer video dimension change.
+   * @param width The new peer video width .
+   * @param height The new peer video height.
+   */
+  @Override
+  public void onUpdatePeerDimensions(DialerCall call, int width, int height) {
+    LogUtil.i("VideoCallPresenter.onUpdatePeerDimensions", "width: %d, height: %d", width, height);
+    if (mVideoCallScreen == null) {
+      LogUtil.e("VideoCallPresenter.onUpdatePeerDimensions", "videoCallScreen is null");
+      return;
+    }
+    if (!call.equals(mPrimaryCall)) {
+      LogUtil.e(
+          "VideoCallPresenter.onUpdatePeerDimensions", "current call is not equal to primary");
+      return;
+    }
+
+    // Change size of display surface to match the peer aspect ratio
+    if (width > 0 && height > 0 && mVideoCallScreen != null) {
+      getRemoteVideoSurfaceTexture().setSourceVideoDimensions(new Point(width, height));
+      mVideoCallScreen.onRemoteVideoDimensionsChanged();
+    }
+  }
+
+  /**
+   * Handles any video quality changes in the call.
+   *
+   * @param call The call which experienced a video quality change.
+   * @param videoQuality The new video call quality.
+   */
+  @Override
+  public void onVideoQualityChanged(DialerCall call, int videoQuality) {
+    // No-op
+  }
+
+  /**
+   * Handles a change to the dimensions of the local camera. Receiving the camera capabilities
+   * triggers the creation of the video
+   *
+   * @param call The call which experienced the camera dimension change.
+   * @param width The new camera video width.
+   * @param height The new camera video height.
+   */
+  @Override
+  public void onCameraDimensionsChange(DialerCall call, int width, int height) {
+    LogUtil.i(
+        "VideoCallPresenter.onCameraDimensionsChange",
+        "call: %s, width: %d, height: %d",
+        call,
+        width,
+        height);
+    if (mVideoCallScreen == null) {
+      LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "ui is null");
+      return;
+    }
+
+    if (!call.equals(mPrimaryCall)) {
+      LogUtil.e("VideoCallPresenter.onCameraDimensionsChange", "not the primary call");
+      return;
+    }
+
+    mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
+    changePreviewDimensions(width, height);
+
+    // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
+    // If it not yet ready, it will be set when when creation completes.
+    Surface surface = getLocalVideoSurfaceTexture().getSavedSurface();
+    if (surface != null) {
+      mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
+      mVideoCall.setPreviewSurface(surface);
+    }
+  }
+
+  /**
+   * Changes the dimensions of the preview surface.
+   *
+   * @param width The new width.
+   * @param height The new height.
+   */
+  private void changePreviewDimensions(int width, int height) {
+    if (mVideoCallScreen == null) {
+      return;
+    }
+
+    // Resize the surface used to display the preview video
+    getLocalVideoSurfaceTexture().setSurfaceDimensions(new Point(width, height));
+    mVideoCallScreen.onLocalVideoDimensionsChanged();
+  }
+
+  /**
+   * Called when call session event is raised.
+   *
+   * @param event The call session event.
+   */
+  @Override
+  public void onCallSessionEvent(int event) {
+    switch (event) {
+      case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
+        LogUtil.v("VideoCallPresenter.onCallSessionEvent", "rx_pause");
+        break;
+      case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
+        LogUtil.v("VideoCallPresenter.onCallSessionEvent", "rx_resume");
+        break;
+      case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
+        LogUtil.v("VideoCallPresenter.onCallSessionEvent", "camera_failure");
+        break;
+      case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
+        LogUtil.v("VideoCallPresenter.onCallSessionEvent", "camera_ready");
+        break;
+      default:
+        LogUtil.v("VideoCallPresenter.onCallSessionEvent", "unknown event = : " + event);
+        break;
+    }
+  }
+
+  /**
+   * Handles a change to the call data usage
+   *
+   * @param dataUsage call data usage value
+   */
+  @Override
+  public void onCallDataUsageChange(long dataUsage) {
+    LogUtil.v("VideoCallPresenter.onCallDataUsageChange", "dataUsage=" + dataUsage);
+  }
+
+  /**
+   * Handles changes to the device orientation.
+   *
+   * @param orientation The screen orientation of the device (one of: {@link
+   *     InCallOrientationEventListener#SCREEN_ORIENTATION_0}, {@link
+   *     InCallOrientationEventListener#SCREEN_ORIENTATION_90}, {@link
+   *     InCallOrientationEventListener#SCREEN_ORIENTATION_180}, {@link
+   *     InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
+   */
+  @Override
+  public void onDeviceOrientationChanged(int orientation) {
+    LogUtil.i(
+        "VideoCallPresenter.onDeviceOrientationChanged",
+        "orientation: %d -> %d",
+        mDeviceOrientation,
+        orientation);
+    mDeviceOrientation = orientation;
+
+    if (mVideoCallScreen == null) {
+      LogUtil.e("VideoCallPresenter.onDeviceOrientationChanged", "videoCallScreen is null");
+      return;
+    }
+
+    Point previewDimensions = getLocalVideoSurfaceTexture().getSurfaceDimensions();
+    if (previewDimensions == null) {
+      return;
+    }
+    LogUtil.v(
+        "VideoCallPresenter.onDeviceOrientationChanged",
+        "orientation: %d, size: %s",
+        orientation,
+        previewDimensions);
+    changePreviewDimensions(previewDimensions.x, previewDimensions.y);
+
+    mVideoCallScreen.onLocalVideoOrientationChanged();
+  }
+
+  /**
+   * Exits fullscreen mode if the current call context has changed to a non-video call.
+   *
+   * @param call The call.
+   */
+  protected void maybeExitFullscreen(DialerCall call) {
+    if (call == null) {
+      return;
+    }
+
+    if (!VideoUtils.isVideoCall(call) || call.getState() == DialerCall.State.INCOMING) {
+      LogUtil.i("VideoCallPresenter.maybeExitFullscreen", "exiting fullscreen");
+      InCallPresenter.getInstance().setFullScreen(false);
+    }
+  }
+
+  /**
+   * Schedules auto-entering of fullscreen mode. Will not enter full screen mode if any of the
+   * following conditions are met: 1. No call 2. DialerCall is not active 3. The current video state
+   * is not bi-directional. 4. Already in fullscreen mode 5. In accessibility mode
+   *
+   * @param call The current call.
+   */
+  protected void maybeAutoEnterFullscreen(DialerCall call) {
+    if (!mIsAutoFullscreenEnabled) {
+      return;
+    }
+
+    if (call == null
+        || call.getState() != DialerCall.State.ACTIVE
+        || !VideoUtils.isBidirectionalVideoCall(call)
+        || InCallPresenter.getInstance().isFullscreen()
+        || (mContext != null && AccessibilityUtil.isTouchExplorationEnabled(mContext))) {
+      // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
+      cancelAutoFullScreen();
+      return;
+    }
+
+    if (mAutoFullScreenPending) {
+      LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "already pending.");
+      return;
+    }
+    LogUtil.v("VideoCallPresenter.maybeAutoEnterFullscreen", "scheduled");
+    mAutoFullScreenPending = true;
+    mHandler.removeCallbacks(mAutoFullscreenRunnable);
+    mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis);
+  }
+
+  /** Cancels pending auto fullscreen mode. */
+  @Override
+  public void cancelAutoFullScreen() {
+    if (!mAutoFullScreenPending) {
+      LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "none pending.");
+      return;
+    }
+    LogUtil.v("VideoCallPresenter.cancelAutoFullScreen", "cancelling pending");
+    mAutoFullScreenPending = false;
+    mHandler.removeCallbacks(mAutoFullscreenRunnable);
+  }
+
+  private void updateRemoteVideoSurfaceDimensions() {
+    Activity activity = mVideoCallScreen.getVideoCallScreenFragment().getActivity();
+    if (activity != null) {
+      Point screenSize = new Point();
+      activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
+      getRemoteVideoSurfaceTexture().setSurfaceDimensions(screenSize);
+    }
+  }
+
+  private static boolean isVideoUpgrade(DialerCall call) {
+    return VideoUtils.hasSentVideoUpgradeRequest(call)
+        || VideoUtils.hasReceivedVideoUpgradeRequest(call);
+  }
+
+  private static boolean isVideoUpgrade(@SessionModificationState int state) {
+    return VideoUtils.hasSentVideoUpgradeRequest(state)
+        || VideoUtils.hasReceivedVideoUpgradeRequest(state);
+  }
+
+  private class LocalDelegate implements VideoSurfaceDelegate {
+    @Override
+    public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
+      if (mVideoCallScreen == null) {
+        LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no UI");
+        return;
+      }
+      if (mVideoCall == null) {
+        LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceCreated", "no video call");
+        return;
+      }
+
+      // If the preview surface has just been created and we have already received camera
+      // capabilities, but not yet set the surface, we will set the surface now.
+      if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
+        mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
+        mVideoCall.setPreviewSurface(videoCallSurface.getSavedSurface());
+      } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()) {
+        enableCamera(mVideoCall, true);
+      }
+    }
+
+    @Override
+    public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
+      if (mVideoCall == null) {
+        LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceReleased", "no video call");
+        return;
+      }
+
+      mVideoCall.setPreviewSurface(null);
+      enableCamera(mVideoCall, false);
+    }
+
+    @Override
+    public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {
+      if (mVideoCall == null) {
+        LogUtil.e("VideoCallPresenter.LocalDelegate.onSurfaceDestroyed", "no video call");
+        return;
+      }
+
+      boolean isChangingConfigurations = InCallPresenter.getInstance().isChangingConfigurations();
+      if (!isChangingConfigurations) {
+        enableCamera(mVideoCall, false);
+      } else {
+        LogUtil.i(
+            "VideoCallPresenter.LocalDelegate.onSurfaceDestroyed",
+            "activity is being destroyed due to configuration changes. Not closing the camera.");
+      }
+    }
+
+    @Override
+    public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
+      VideoCallPresenter.this.onSurfaceClick();
+    }
+  }
+
+  private class RemoteDelegate implements VideoSurfaceDelegate {
+    @Override
+    public void onSurfaceCreated(VideoSurfaceTexture videoCallSurface) {
+      if (mVideoCallScreen == null) {
+        LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no UI");
+        return;
+      }
+      if (mVideoCall == null) {
+        LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceCreated", "no video call");
+        return;
+      }
+      mVideoCall.setDisplaySurface(videoCallSurface.getSavedSurface());
+    }
+
+    @Override
+    public void onSurfaceReleased(VideoSurfaceTexture videoCallSurface) {
+      if (mVideoCall == null) {
+        LogUtil.e("VideoCallPresenter.RemoteDelegate.onSurfaceReleased", "no video call");
+        return;
+      }
+      mVideoCall.setDisplaySurface(null);
+    }
+
+    @Override
+    public void onSurfaceDestroyed(VideoSurfaceTexture videoCallSurface) {}
+
+    @Override
+    public void onSurfaceClick(VideoSurfaceTexture videoCallSurface) {
+      VideoCallPresenter.this.onSurfaceClick();
+    }
+  }
+
+  /** Defines the state of the preview surface negotiation with the telephony layer. */
+  private static class PreviewSurfaceState {
+
+    /**
+     * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet started.
+     */
+    private static final int NONE = 0;
+
+    /**
+     * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet been
+     * received.
+     */
+    private static final int CAMERA_SET = 1;
+
+    /**
+     * The camera capabilties have been received from telephony, but the surface has not yet been
+     * set on the {@link VideoCall}.
+     */
+    private static final int CAPABILITIES_RECEIVED = 2;
+
+    /** The surface has been set on the {@link VideoCall}. */
+    private static final int SURFACE_SET = 3;
+  }
+}