/*
 * 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.graphics.Matrix;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.android.dialer.R;
import com.android.phone.common.animation.AnimUtils;
import com.google.common.base.Objects;

/**
 * Fragment containing video calling surfaces.
 */
public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
        VideoCallPresenter.VideoCallUi> implements VideoCallPresenter.VideoCallUi {
    private static final String TAG = VideoCallFragment.class.getSimpleName();
    private static final boolean DEBUG = false;

    /**
     * Used to indicate that the surface dimensions are not set.
     */
    private static final int DIMENSIONS_NOT_SET = -1;

    /**
     * Surface ID for the display surface.
     */
    public static final int SURFACE_DISPLAY = 1;

    /**
     * Surface ID for the preview surface.
     */
    public static final int SURFACE_PREVIEW = 2;

    /**
     * Used to indicate that the UI rotation is unknown.
     */
    public static final int ORIENTATION_UNKNOWN = -1;

    // Static storage used to retain the video surfaces across Activity restart.
    // TextureViews are not parcelable, so it is not possible to store them in the saved state.
    private static boolean sVideoSurfacesInUse = false;
    private static VideoCallSurface sPreviewSurface = null;
    private static VideoCallSurface sDisplaySurface = null;
    private static Point sDisplaySize = null;

    /**
     * {@link ViewStub} holding the video call surfaces.  This is the parent for the
     * {@link VideoCallFragment}.  Used to ensure that the video surfaces are only inflated when
     * required.
     */
    private ViewStub mVideoViewsStub;

    /**
     * Inflated view containing the video call surfaces represented by the {@link ViewStub}.
     */
    private View mVideoViews;

    /**
     * The {@link FrameLayout} containing the preview surface.
     */
    private View mPreviewVideoContainer;

    /**
     * Icon shown to indicate that the outgoing camera has been turned off.
     */
    private View mCameraOff;

    /**
     * {@link ImageView} containing the user's profile photo.
     */
    private ImageView mPreviewPhoto;

    /**
     * {@code True} when the layout of the activity has been completed.
     */
    private boolean mIsLayoutComplete = false;

    /**
     * {@code True} if in landscape mode.
     */
    private boolean mIsLandscape;

    private int mAnimationDuration;

    /**
     * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and
     * {@link Surface}.  Used to manage the lifecycle of these objects across device orientation
     * changes.
     */
    private static class VideoCallSurface implements TextureView.SurfaceTextureListener,
            View.OnClickListener, View.OnAttachStateChangeListener {
        private int mSurfaceId;
        private VideoCallPresenter mPresenter;
        private TextureView mTextureView;
        private SurfaceTexture mSavedSurfaceTexture;
        private Surface mSavedSurface;
        private boolean mIsDoneWithSurface;
        private int mWidth = DIMENSIONS_NOT_SET;
        private int mHeight = DIMENSIONS_NOT_SET;

        /**
         * Creates an instance of a {@link VideoCallSurface}.
         *
         * @param surfaceId The surface ID of the surface.
         * @param textureView The {@link TextureView} for the surface.
         */
        public VideoCallSurface(VideoCallPresenter presenter, int surfaceId,
                TextureView textureView) {
            this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
        }

        /**
         * Creates an instance of a {@link VideoCallSurface}.
         *
         * @param surfaceId The surface ID of the surface.
         * @param textureView The {@link TextureView} for the surface.
         * @param width The width of the surface.
         * @param height The height of the surface.
         */
        public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView,
                int width, int height) {
            Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId +
                    " width=" + width + " height=" + height);
            mPresenter = presenter;
            mWidth = width;
            mHeight = height;
            mSurfaceId = surfaceId;

            recreateView(textureView);
        }

        /**
         * Recreates a {@link VideoCallSurface} after a device orientation change.  Re-applies the
         * saved {@link SurfaceTexture} to the
         *
         * @param view The {@link TextureView}.
         */
        public void recreateView(TextureView view) {
            if (DEBUG) {
                Log.i(TAG, "recreateView: " + view);
            }

            if (mTextureView == view) {
                return;
            }

            mTextureView = view;
            mTextureView.setSurfaceTextureListener(this);
            mTextureView.setOnClickListener(this);

            final boolean areSameSurfaces =
                    Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture());
            Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture
                    + " areSameSurfaces=" + areSameSurfaces);
            if (mSavedSurfaceTexture != null && !areSameSurfaces) {
                mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
                if (createSurface(mWidth, mHeight)) {
                    onSurfaceCreated();
                }
            }
            mIsDoneWithSurface = false;
        }

        public void resetPresenter(VideoCallPresenter presenter) {
            Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter="
                    + presenter);
            mPresenter = presenter;
        }

        /**
         * Handles {@link SurfaceTexture} callback to indicate that a {@link SurfaceTexture} has
         * been successfully created.
         *
         * @param surfaceTexture The {@link SurfaceTexture} which has been created.
         * @param width The width of the {@link SurfaceTexture}.
         * @param height The height of the {@link SurfaceTexture}.
         */
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
                int height) {
            boolean surfaceCreated;
            if (DEBUG) {
                Log.i(TAG, "onSurfaceTextureAvailable: " + surfaceTexture);
            }
            // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
            // If a saved {@link SurfaceTexture} is available, we are re-creating after an
            // orientation change.
            Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture="
                    + surfaceTexture + " width=" + width
                    + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture);
            Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter);
            if (mSavedSurfaceTexture == null) {
                mSavedSurfaceTexture = surfaceTexture;
                surfaceCreated = createSurface(width, height);
            } else {
                // A saved SurfaceTexture was found.
                Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface...");
                mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
                surfaceCreated = true;
            }

            // Inform presenter that the surface is available.
            if (surfaceCreated) {
                onSurfaceCreated();
            }
        }

        private void onSurfaceCreated() {
            if (mPresenter != null) {
                mPresenter.onSurfaceCreated(mSurfaceId);
            } else {
                Log.e(this, "onSurfaceTextureAvailable: Presenter is null");
            }
        }

        /**
         * Handles a change in the {@link SurfaceTexture}'s size.
         *
         * @param surfaceTexture The {@link SurfaceTexture}.
         * @param width The new width.
         * @param height The new height.
         */
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width,
                int height) {
            // Not handled
        }

        /**
         * Handles {@link SurfaceTexture} destruct callback, indicating that it has been destroyed.
         *
         * @param surfaceTexture The {@link SurfaceTexture}.
         * @return {@code True} if the {@link TextureView} can release the {@link SurfaceTexture}.
         */
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            /**
             * Destroying the surface texture; inform the presenter so it can null the surfaces.
             */
            Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture="
                    + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture
                    + " SavedSurface=" + mSavedSurface);
            Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter);

            // Notify presenter if it is not null.
            onSurfaceDestroyed();

            if (mIsDoneWithSurface) {
                onSurfaceReleased();
                if (mSavedSurface != null) {
                    mSavedSurface.release();
                    mSavedSurface = null;
                }
            }
            return mIsDoneWithSurface;
        }

        private void onSurfaceDestroyed() {
            if (mPresenter != null) {
                mPresenter.onSurfaceDestroyed(mSurfaceId);
            } else {
                Log.e(this, "onSurfaceTextureDestroyed: Presenter is null.");
            }
        }

        /**
         * Handles {@link SurfaceTexture} update callback.
         * @param surface
         */
        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            // Not Handled
        }

        @Override
        public void onViewAttachedToWindow(View v) {
            if (DEBUG) {
                Log.i(TAG, "OnViewAttachedToWindow");
            }
            if (mSavedSurfaceTexture != null) {
                mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
            }
        }

        @Override
        public void onViewDetachedFromWindow(View v) {}

        /**
         * Retrieves the current {@link TextureView}.
         *
         * @return The {@link TextureView}.
         */
        public TextureView getTextureView() {
            return mTextureView;
        }

        /**
         * Called by the user presenter to indicate that the surface is no longer required due to a
         * change in video state.  Releases and clears out the saved surface and surface textures.
         */
        public void setDoneWithSurface() {
            Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface
                    + " SavedSurfaceTexture=" + mSavedSurfaceTexture);
            mIsDoneWithSurface = true;
            if (mTextureView != null && mTextureView.isAvailable()) {
                return;
            }

            if (mSavedSurface != null) {
                onSurfaceReleased();
                mSavedSurface.release();
                mSavedSurface = null;
            }
            if (mSavedSurfaceTexture != null) {
                mSavedSurfaceTexture.release();
                mSavedSurfaceTexture = null;
            }
        }

        private void onSurfaceReleased() {
            if (mPresenter != null) {
                mPresenter.onSurfaceReleased(mSurfaceId);
            } else {
                Log.d(this, "setDoneWithSurface: Presenter is null.");
            }
        }

        /**
         * Retrieves the saved surface instance.
         *
         * @return The surface.
         */
        public Surface getSurface() {
            return mSavedSurface;
        }

        /**
         * Sets the dimensions of the surface.
         *
         * @param width The width of the surface, in pixels.
         * @param height The height of the surface, in pixels.
         */
        public void setSurfaceDimensions(int width, int height) {
            Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height);
            mWidth = width;
            mHeight = height;

            if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
                    && mSavedSurfaceTexture != null) {
                Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null.");
                mSavedSurfaceTexture.setDefaultBufferSize(width, height);
            }
        }

        /**
         * Creates the {@link Surface}, adjusting the {@link SurfaceTexture} buffer size.
         * @param width The width of the surface to create.
         * @param height The height of the surface to create.
         */
        private boolean createSurface(int width, int height) {
            Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture
                    + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height);
            if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
                    && mSavedSurfaceTexture != null) {
                mSavedSurfaceTexture.setDefaultBufferSize(width, height);
                mSavedSurface = new Surface(mSavedSurfaceTexture);
                return true;
            }
            return false;
        }

        /**
         * Handles a user clicking the surface, which is the trigger to toggle the full screen
         * Video UI.
         *
         * @param view The view receiving the click.
         */
        @Override
        public void onClick(View view) {
            if (mPresenter != null) {
                mPresenter.onSurfaceClick(mSurfaceId);
            } else {
                Log.e(this, "onClick: Presenter is null.");
            }
        }

        /**
         * Returns the dimensions of the surface.
         *
         * @return The dimensions of the surface.
         */
        public Point getSurfaceDimensions() {
            return new Point(mWidth, mHeight);
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
    }

    /**
     * Handles creation of the activity and initialization of the presenter.
     *
     * @param savedInstanceState The saved instance state.
     */
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape);

        Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
        getPresenter().init(getActivity());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);

        final View view = inflater.inflate(R.layout.video_call_fragment, container, false);

        return view;
    }

    /**
     * Centers the display view vertically for portrait orientations. The view is centered within
     * the available space not occupied by the call card. This is a no-op for landscape mode.
     *
     * @param displayVideo The video view to center.
     */
    private void centerDisplayView(View displayVideo) {
        if (!mIsLandscape) {
            ViewGroup.LayoutParams p = displayVideo.getLayoutParams();
            int height = p.height;

            float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
            // If space beside call card is zeo, layout hasn't happened yet so there is no point
            // in attempting to center the view.
            if (Math.abs(spaceBesideCallCard - 0.0f) < 0.0001) {
                return;
            }
            float videoViewTranslation = height / 2  - spaceBesideCallCard / 2;
            displayVideo.setTranslationY(videoViewTranslation);
        }
    }

    /**
     * After creation of the fragment view, retrieves the required views.
     *
     * @param view The fragment view.
     * @param savedInstanceState The saved instance state.
     */
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse);

        mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(this, "onStop:");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(this, "onPause:");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(this, "onDestroyView:");
    }

    /**
     * Creates the presenter for the {@link VideoCallFragment}.
     * @return The presenter instance.
     */
    @Override
    public VideoCallPresenter createPresenter() {
        Log.d(this, "createPresenter");
        VideoCallPresenter presenter = new VideoCallPresenter();
        onPresenterChanged(presenter);
        return presenter;
    }

    /**
     * @return The user interface for the presenter, which is this fragment.
     */
    @Override
    public VideoCallPresenter.VideoCallUi getUi() {
        return this;
    }

    /**
     * Inflate video surfaces.
     *
     * @param show {@code True} if the video surfaces should be shown.
     */
    private void inflateVideoUi(boolean show) {
        int visibility = show ? View.VISIBLE : View.GONE;
        getView().setVisibility(visibility);

        if (show) {
            inflateVideoCallViews();
        }

        if (mVideoViews != null) {
            mVideoViews.setVisibility(visibility);
        }
    }

    /**
     * Hides and shows the incoming video view and changes the outgoing video view's state based on
     * whether outgoing view is enabled or not.
     */
    public void showVideoViews(boolean previewPaused, boolean showIncoming) {
        inflateVideoUi(true);

        View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo);
        if (incomingVideoView != null) {
            incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE);
        }
        if (mCameraOff != null) {
            mCameraOff.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
        }
        if (mPreviewPhoto != null) {
            mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
        }
    }

    /**
     * Hide all video views.
     */
    public void hideVideoUi() {
        inflateVideoUi(false);
    }

    /**
     * Cleans up the video telephony surfaces.  Used when the presenter indicates a change to an
     * audio-only state.  Since the surfaces are static, it is important to ensure they are cleaned
     * up promptly.
     */
    @Override
    public void cleanupSurfaces() {
        Log.d(this, "cleanupSurfaces");
        if (sDisplaySurface != null) {
            sDisplaySurface.setDoneWithSurface();
            sDisplaySurface = null;
        }
        if (sPreviewSurface != null) {
            sPreviewSurface.setDoneWithSurface();
            sPreviewSurface = null;
        }
        sVideoSurfacesInUse = false;
    }

    @Override
    public ImageView getPreviewPhotoView() {
        return mPreviewPhoto;
    }

    /**
     * Adjusts the location of the video preview view by the specified offset.
     *
     * @param shiftUp {@code true} if the preview should shift up, {@code false} if it should shift
     *      down.
     * @param offset The offset.
     */
    @Override
    public void adjustPreviewLocation(boolean shiftUp, int offset) {
        if (sPreviewSurface == null || mPreviewVideoContainer == null) {
            return;
        }

        // Set the position of the secondary call info card to its starting location.
        mPreviewVideoContainer.setTranslationY(shiftUp ? 0 : -offset);

        // Animate the secondary card info slide up/down as it appears and disappears.
        mPreviewVideoContainer.animate()
                .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
                .setDuration(mAnimationDuration)
                .translationY(shiftUp ? -offset : 0)
                .start();
    }

    private void onPresenterChanged(VideoCallPresenter presenter) {
        Log.d(this, "onPresenterChanged: Presenter=" + presenter);
        if (sDisplaySurface != null) {
            sDisplaySurface.resetPresenter(presenter);;
        }
        if (sPreviewSurface != null) {
            sPreviewSurface.resetPresenter(presenter);
        }
    }

    /**
     * @return {@code True} if the display video surface has been created.
     */
    @Override
    public boolean isDisplayVideoSurfaceCreated() {
        boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null;
        Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret);
        return ret;
    }

    /**
     * @return {@code True} if the preview video surface has been created.
     */
    @Override
    public boolean isPreviewVideoSurfaceCreated() {
        boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null;
        Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret);
        return ret;
    }

    /**
     * {@link android.view.Surface} on which incoming video for a video call is displayed.
     * {@code Null} until the video views {@link android.view.ViewStub} is inflated.
     */
    @Override
    public Surface getDisplayVideoSurface() {
        return sDisplaySurface == null ? null : sDisplaySurface.getSurface();
    }

    /**
     * {@link android.view.Surface} on which a preview of the outgoing video for a video call is
     * displayed.  {@code Null} until the video views {@link android.view.ViewStub} is inflated.
     */
    @Override
    public Surface getPreviewVideoSurface() {
        return sPreviewSurface == null ? null : sPreviewSurface.getSurface();
    }

    /**
     * Changes the dimensions of the preview surface.  Called when the dimensions change due to a
     * device orientation change.
     *
     * @param width The new width.
     * @param height The new height.
     */
    @Override
    public void setPreviewSize(int width, int height) {
        Log.d(this, "setPreviewSize: width=" + width + " height=" + height);
        if (sPreviewSurface != null) {
            TextureView preview = sPreviewSurface.getTextureView();

            if (preview == null ) {
                return;
            }

            // Set the dimensions of both the video surface and the FrameLayout containing it.
            ViewGroup.LayoutParams params = preview.getLayoutParams();
            params.width = width;
            params.height = height;
            preview.setLayoutParams(params);

            if (mPreviewVideoContainer != null) {
                ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams();
                containerParams.width = width;
                containerParams.height = height;
                mPreviewVideoContainer.setLayoutParams(containerParams);
            }

            // The width and height are interchanged outside of this method based on the current
            // orientation, so we can transform using "width", which will be either the width or
            // the height.
            Matrix transform = new Matrix();
            transform.setScale(-1, 1, width/2, 0);
            preview.setTransform(transform);
        }
    }

    /**
     * Sets the rotation of the preview surface.  Called when the dimensions change due to a
     * device orientation change.
     *
     * Please note that the screen orientation passed in is subtracted from 360 to get the actual
     * preview rotation values.
     *
     * @param rotation The screen orientation. 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 setPreviewRotation(int orientation) {
        Log.d(this, "setPreviewRotation: orientation=" + orientation);
        if (sPreviewSurface != null) {
            TextureView preview = sPreviewSurface.getTextureView();

            if (preview == null ) {
                return;
            }

            preview.setRotation(orientation);
        }
    }

    @Override
    public void setPreviewSurfaceSize(int width, int height) {
        final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
        Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height +
                " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable);
        if (isPreviewSurfaceAvailable) {
            sPreviewSurface.setSurfaceDimensions(width, height);
        }
    }

    /**
     * returns UI's current orientation.
     */
    @Override
    public int getCurrentRotation() {
        try {
            return getActivity().getWindowManager().getDefaultDisplay().getRotation();
        } catch (Exception e) {
            Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e);
        }
        return ORIENTATION_UNKNOWN;
    }

    /**
     * Changes the dimensions of the display video surface. Called when the dimensions change due to
     * a peer resolution update
     *
     * @param width The new width.
     * @param height The new height.
     */
    @Override
    public void setDisplayVideoSize(int width, int height) {
        Log.v(this, "setDisplayVideoSize: width=" + width + " height=" + height);
        if (sDisplaySurface != null) {
            TextureView displayVideo = sDisplaySurface.getTextureView();
            if (displayVideo == null) {
                Log.e(this, "Display Video texture view is null. Bail out");
                return;
            }
            sDisplaySize = new Point(width, height);
            setSurfaceSizeAndTranslation(displayVideo, sDisplaySize);
        } else {
            Log.e(this, "Display Video Surface is null. Bail out");
        }
    }

    /**
     * Determines the size of the device screen.
     *
     * @return {@link Point} specifying the width and height of the screen.
     */
    @Override
    public Point getScreenSize() {
        // Get current screen size.
        Display display = getActivity().getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);

        return size;
    }

    /**
     * Determines the size of the preview surface.
     *
     * @return {@link Point} specifying the width and height of the preview surface.
     */
    @Override
    public Point getPreviewSize() {
        if (sPreviewSurface == null) {
            return null;
        }
        return sPreviewSurface.getSurfaceDimensions();
    }

    /**
     * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
     * and creates {@link VideoCallSurface} instances to track the surfaces.
     */
    private void inflateVideoCallViews() {
        Log.d(this, "inflateVideoCallViews");
        if (mVideoViews == null ) {
            mVideoViews = mVideoViewsStub.inflate();
        }

        if (mVideoViews != null) {
            mPreviewVideoContainer = mVideoViews.findViewById(R.id.previewVideoContainer);
            mCameraOff = mVideoViews.findViewById(R.id.previewCameraOff);
            mPreviewPhoto = (ImageView) mVideoViews.findViewById(R.id.previewProfilePhoto);

            TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);

            Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
            //If peer adjusted screen size is not available, set screen size to default display size
            Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize;
            setSurfaceSizeAndTranslation(displaySurface, screenSize);

            if (!sVideoSurfacesInUse) {
                // Where the video surfaces are not already in use (first time creating them),
                // setup new VideoCallSurface instances to track them.
                Log.d(this, " inflateVideoCallViews screenSize" + screenSize);

                sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY,
                        (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
                        screenSize.y);
                sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW,
                        (TextureView) mVideoViews.findViewById(R.id.previewVideo));
                sVideoSurfacesInUse = true;
            } else {
                // In this case, the video surfaces are already in use (we are recreating the
                // Fragment after a destroy/create cycle resulting from a rotation.
                sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById(
                        R.id.incomingVideo));
                sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById(
                        R.id.previewVideo));
            }

            // Attempt to center the incoming video view, if it is in the layout.
            final ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
            observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    // Check if the layout includes the incoming video surface -- this will only be the
                    // case for a video call.
                    View displayVideo = mVideoViews.findViewById(R.id.incomingVideo);
                    if (displayVideo != null) {
                        centerDisplayView(displayVideo);
                    }
                    mIsLayoutComplete = true;

                    // Remove the listener so we don't continually re-layout.
                    ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
                    if (observer.isAlive()) {
                        observer.removeOnGlobalLayoutListener(this);
                    }
                }
            });
        }
    }

    /**
     * Resizes a surface so that it has the same size as the full screen and so that it is
     * centered vertically below the call card.
     *
     * @param textureView The {@link TextureView} to resize and position.
     * @param size The size of the screen.
     */
    private void setSurfaceSizeAndTranslation(TextureView textureView, Point size) {
        // Set the surface to have that size.
        ViewGroup.LayoutParams params = textureView.getLayoutParams();
        params.width = size.x;
        params.height = size.y;
        textureView.setLayoutParams(params);
        Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" +
                mIsLayoutComplete + "IsLandscape=" + mIsLandscape);

        // It is only possible to center the display view if layout of the views has completed.
        // It is only after layout is complete that the dimensions of the Call Card has been
        // established, which is a prerequisite to centering the view.
        // Incoming video calls will center the view
        if (mIsLayoutComplete) {
            centerDisplayView(textureView);
        }
    }
}
