/* * 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 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); } } }