Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.incallui;
     18 
     19 import android.graphics.Matrix;
     20 import android.graphics.Point;
     21 import android.graphics.SurfaceTexture;
     22 import android.os.Bundle;
     23 import android.view.Display;
     24 import android.view.LayoutInflater;
     25 import android.view.Surface;
     26 import android.view.TextureView;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.ViewStub;
     30 import android.view.ViewTreeObserver;
     31 import android.widget.FrameLayout;
     32 import android.widget.ImageView;
     33 
     34 import com.google.common.base.Objects;
     35 
     36 /**
     37  * Fragment containing video calling surfaces.
     38  */
     39 public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
     40         VideoCallPresenter.VideoCallUi> implements VideoCallPresenter.VideoCallUi {
     41     private static final String TAG = VideoCallFragment.class.getSimpleName();
     42     private static final boolean DEBUG = false;
     43 
     44     /**
     45      * Used to indicate that the surface dimensions are not set.
     46      */
     47     private static final int DIMENSIONS_NOT_SET = -1;
     48 
     49     /**
     50      * Surface ID for the display surface.
     51      */
     52     public static final int SURFACE_DISPLAY = 1;
     53 
     54     /**
     55      * Surface ID for the preview surface.
     56      */
     57     public static final int SURFACE_PREVIEW = 2;
     58 
     59     /**
     60      * Used to indicate that the UI rotation is unknown.
     61      */
     62     public static final int ORIENTATION_UNKNOWN = -1;
     63 
     64     // Static storage used to retain the video surfaces across Activity restart.
     65     // TextureViews are not parcelable, so it is not possible to store them in the saved state.
     66     private static boolean sVideoSurfacesInUse = false;
     67     private static VideoCallSurface sPreviewSurface = null;
     68     private static VideoCallSurface sDisplaySurface = null;
     69     private static Point sDisplaySize = null;
     70 
     71     /**
     72      * {@link ViewStub} holding the video call surfaces.  This is the parent for the
     73      * {@link VideoCallFragment}.  Used to ensure that the video surfaces are only inflated when
     74      * required.
     75      */
     76     private ViewStub mVideoViewsStub;
     77 
     78     /**
     79      * Inflated view containing the video call surfaces represented by the {@link ViewStub}.
     80      */
     81     private View mVideoViews;
     82 
     83     /**
     84      * The {@link FrameLayout} containing the preview surface.
     85      */
     86     private View mPreviewVideoContainer;
     87 
     88     /**
     89      * Icon shown to indicate that the outgoing camera has been turned off.
     90      */
     91     private View mCameraOff;
     92 
     93     /**
     94      * {@link ImageView} containing the user's profile photo.
     95      */
     96     private ImageView mPreviewPhoto;
     97 
     98     /**
     99      * {@code True} when the layout of the activity has been completed.
    100      */
    101     private boolean mIsLayoutComplete = false;
    102 
    103     /**
    104      * {@code True} if in landscape mode.
    105      */
    106     private boolean mIsLandscape;
    107 
    108     /**
    109      * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and
    110      * {@link Surface}.  Used to manage the lifecycle of these objects across device orientation
    111      * changes.
    112      */
    113     private static class VideoCallSurface implements TextureView.SurfaceTextureListener,
    114             View.OnClickListener, View.OnAttachStateChangeListener {
    115         private int mSurfaceId;
    116         private VideoCallPresenter mPresenter;
    117         private TextureView mTextureView;
    118         private SurfaceTexture mSavedSurfaceTexture;
    119         private Surface mSavedSurface;
    120         private boolean mIsDoneWithSurface;
    121         private int mWidth = DIMENSIONS_NOT_SET;
    122         private int mHeight = DIMENSIONS_NOT_SET;
    123 
    124         /**
    125          * Creates an instance of a {@link VideoCallSurface}.
    126          *
    127          * @param surfaceId The surface ID of the surface.
    128          * @param textureView The {@link TextureView} for the surface.
    129          */
    130         public VideoCallSurface(VideoCallPresenter presenter, int surfaceId,
    131                 TextureView textureView) {
    132             this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
    133         }
    134 
    135         /**
    136          * Creates an instance of a {@link VideoCallSurface}.
    137          *
    138          * @param surfaceId The surface ID of the surface.
    139          * @param textureView The {@link TextureView} for the surface.
    140          * @param width The width of the surface.
    141          * @param height The height of the surface.
    142          */
    143         public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView,
    144                 int width, int height) {
    145             Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId +
    146                     " width=" + width + " height=" + height);
    147             mPresenter = presenter;
    148             mWidth = width;
    149             mHeight = height;
    150             mSurfaceId = surfaceId;
    151 
    152             recreateView(textureView);
    153         }
    154 
    155         /**
    156          * Recreates a {@link VideoCallSurface} after a device orientation change.  Re-applies the
    157          * saved {@link SurfaceTexture} to the
    158          *
    159          * @param view The {@link TextureView}.
    160          */
    161         public void recreateView(TextureView view) {
    162             if (DEBUG) {
    163                 Log.i(TAG, "recreateView: " + view);
    164             }
    165 
    166             if (mTextureView == view) {
    167                 return;
    168             }
    169 
    170             mTextureView = view;
    171             mTextureView.setSurfaceTextureListener(this);
    172             mTextureView.setOnClickListener(this);
    173 
    174             final boolean areSameSurfaces =
    175                     Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture());
    176             Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture
    177                     + " areSameSurfaces=" + areSameSurfaces);
    178             if (mSavedSurfaceTexture != null && !areSameSurfaces) {
    179                 mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
    180                 if (createSurface(mWidth, mHeight)) {
    181                     onSurfaceCreated();
    182                 }
    183             }
    184             mIsDoneWithSurface = false;
    185         }
    186 
    187         public void resetPresenter(VideoCallPresenter presenter) {
    188             Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter="
    189                     + presenter);
    190             mPresenter = presenter;
    191         }
    192 
    193         /**
    194          * Handles {@link SurfaceTexture} callback to indicate that a {@link SurfaceTexture} has
    195          * been successfully created.
    196          *
    197          * @param surfaceTexture The {@link SurfaceTexture} which has been created.
    198          * @param width The width of the {@link SurfaceTexture}.
    199          * @param height The height of the {@link SurfaceTexture}.
    200          */
    201         @Override
    202         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
    203                 int height) {
    204             boolean surfaceCreated;
    205             if (DEBUG) {
    206                 Log.i(TAG, "onSurfaceTextureAvailable: " + surfaceTexture);
    207             }
    208             // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
    209             // If a saved {@link SurfaceTexture} is available, we are re-creating after an
    210             // orientation change.
    211             Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture="
    212                     + surfaceTexture + " width=" + width
    213                     + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture);
    214             Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter);
    215             if (mSavedSurfaceTexture == null) {
    216                 mSavedSurfaceTexture = surfaceTexture;
    217                 surfaceCreated = createSurface(width, height);
    218             } else {
    219                 // A saved SurfaceTexture was found.
    220                 Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface...");
    221                 mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
    222                 surfaceCreated = true;
    223             }
    224 
    225             // Inform presenter that the surface is available.
    226             if (surfaceCreated) {
    227                 onSurfaceCreated();
    228             }
    229         }
    230 
    231         private void onSurfaceCreated() {
    232             if (mPresenter != null) {
    233                 mPresenter.onSurfaceCreated(mSurfaceId);
    234             } else {
    235                 Log.e(this, "onSurfaceTextureAvailable: Presenter is null");
    236             }
    237         }
    238 
    239         /**
    240          * Handles a change in the {@link SurfaceTexture}'s size.
    241          *
    242          * @param surfaceTexture The {@link SurfaceTexture}.
    243          * @param width The new width.
    244          * @param height The new height.
    245          */
    246         @Override
    247         public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width,
    248                 int height) {
    249             // Not handled
    250         }
    251 
    252         /**
    253          * Handles {@link SurfaceTexture} destruct callback, indicating that it has been destroyed.
    254          *
    255          * @param surfaceTexture The {@link SurfaceTexture}.
    256          * @return {@code True} if the {@link TextureView} can release the {@link SurfaceTexture}.
    257          */
    258         @Override
    259         public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    260             /**
    261              * Destroying the surface texture; inform the presenter so it can null the surfaces.
    262              */
    263             Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture="
    264                     + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture
    265                     + " SavedSurface=" + mSavedSurface);
    266             Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter);
    267 
    268             // Notify presenter if it is not null.
    269             onSurfaceDestroyed();
    270 
    271             if (mIsDoneWithSurface) {
    272                 onSurfaceReleased();
    273                 if (mSavedSurface != null) {
    274                     mSavedSurface.release();
    275                     mSavedSurface = null;
    276                 }
    277             }
    278             return mIsDoneWithSurface;
    279         }
    280 
    281         private void onSurfaceDestroyed() {
    282             if (mPresenter != null) {
    283                 mPresenter.onSurfaceDestroyed(mSurfaceId);
    284             } else {
    285                 Log.e(this, "onSurfaceTextureDestroyed: Presenter is null.");
    286             }
    287         }
    288 
    289         /**
    290          * Handles {@link SurfaceTexture} update callback.
    291          * @param surface
    292          */
    293         @Override
    294         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
    295             // Not Handled
    296         }
    297 
    298         @Override
    299         public void onViewAttachedToWindow(View v) {
    300             if (DEBUG) {
    301                 Log.i(TAG, "OnViewAttachedToWindow");
    302             }
    303             if (mSavedSurfaceTexture != null) {
    304                 mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
    305             }
    306         }
    307 
    308         @Override
    309         public void onViewDetachedFromWindow(View v) {}
    310 
    311         /**
    312          * Retrieves the current {@link TextureView}.
    313          *
    314          * @return The {@link TextureView}.
    315          */
    316         public TextureView getTextureView() {
    317             return mTextureView;
    318         }
    319 
    320         /**
    321          * Called by the user presenter to indicate that the surface is no longer required due to a
    322          * change in video state.  Releases and clears out the saved surface and surface textures.
    323          */
    324         public void setDoneWithSurface() {
    325             Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface
    326                     + " SavedSurfaceTexture=" + mSavedSurfaceTexture);
    327             mIsDoneWithSurface = true;
    328             if (mTextureView != null && mTextureView.isAvailable()) {
    329                 return;
    330             }
    331 
    332             if (mSavedSurface != null) {
    333                 onSurfaceReleased();
    334                 mSavedSurface.release();
    335                 mSavedSurface = null;
    336             }
    337             if (mSavedSurfaceTexture != null) {
    338                 mSavedSurfaceTexture.release();
    339                 mSavedSurfaceTexture = null;
    340             }
    341         }
    342 
    343         private void onSurfaceReleased() {
    344             if (mPresenter != null) {
    345                 mPresenter.onSurfaceReleased(mSurfaceId);
    346             } else {
    347                 Log.d(this, "setDoneWithSurface: Presenter is null.");
    348             }
    349         }
    350 
    351         /**
    352          * Retrieves the saved surface instance.
    353          *
    354          * @return The surface.
    355          */
    356         public Surface getSurface() {
    357             return mSavedSurface;
    358         }
    359 
    360         /**
    361          * Sets the dimensions of the surface.
    362          *
    363          * @param width The width of the surface, in pixels.
    364          * @param height The height of the surface, in pixels.
    365          */
    366         public void setSurfaceDimensions(int width, int height) {
    367             Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height);
    368             mWidth = width;
    369             mHeight = height;
    370 
    371             if (mSavedSurfaceTexture != null) {
    372                 Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null.");
    373                 createSurface(width, height);
    374             }
    375         }
    376 
    377         /**
    378          * Creates the {@link Surface}, adjusting the {@link SurfaceTexture} buffer size.
    379          * @param width The width of the surface to create.
    380          * @param height The height of the surface to create.
    381          */
    382         private boolean createSurface(int width, int height) {
    383             Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture
    384                     + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height);
    385             if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
    386                     && mSavedSurfaceTexture != null) {
    387                 mSavedSurfaceTexture.setDefaultBufferSize(width, height);
    388                 mSavedSurface = new Surface(mSavedSurfaceTexture);
    389                 return true;
    390             }
    391             return false;
    392         }
    393 
    394         /**
    395          * Handles a user clicking the surface, which is the trigger to toggle the full screen
    396          * Video UI.
    397          *
    398          * @param view The view receiving the click.
    399          */
    400         @Override
    401         public void onClick(View view) {
    402             if (mPresenter != null) {
    403                 mPresenter.onSurfaceClick(mSurfaceId);
    404             } else {
    405                 Log.e(this, "onClick: Presenter is null.");
    406             }
    407         }
    408 
    409         /**
    410          * Returns the dimensions of the surface.
    411          *
    412          * @return The dimensions of the surface.
    413          */
    414         public Point getSurfaceDimensions() {
    415             return new Point(mWidth, mHeight);
    416         }
    417     };
    418 
    419     @Override
    420     public void onCreate(Bundle savedInstanceState) {
    421         super.onCreate(savedInstanceState);
    422     }
    423 
    424     /**
    425      * Handles creation of the activity and initialization of the presenter.
    426      *
    427      * @param savedInstanceState The saved instance state.
    428      */
    429     @Override
    430     public void onActivityCreated(Bundle savedInstanceState) {
    431         super.onActivityCreated(savedInstanceState);
    432 
    433         mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape);
    434 
    435         Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
    436         getPresenter().init(getActivity());
    437     }
    438 
    439     @Override
    440     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    441             Bundle savedInstanceState) {
    442         super.onCreateView(inflater, container, savedInstanceState);
    443 
    444         final View view = inflater.inflate(R.layout.video_call_fragment, container, false);
    445 
    446         return view;
    447     }
    448 
    449     /**
    450      * Centers the display view vertically for portrait orientations. The view is centered within
    451      * the available space not occupied by the call card. This is a no-op for landscape mode.
    452      *
    453      * @param displayVideo The video view to center.
    454      */
    455     private void centerDisplayView(View displayVideo) {
    456         if (!mIsLandscape) {
    457             float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
    458             float videoViewTranslation = displayVideo.getHeight() / 2
    459                     - spaceBesideCallCard / 2;
    460             displayVideo.setTranslationY(videoViewTranslation);
    461         }
    462     }
    463 
    464     /**
    465      * After creation of the fragment view, retrieves the required views.
    466      *
    467      * @param view The fragment view.
    468      * @param savedInstanceState The saved instance state.
    469      */
    470     @Override
    471     public void onViewCreated(View view, Bundle savedInstanceState) {
    472         super.onViewCreated(view, savedInstanceState);
    473         Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse);
    474 
    475         mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
    476 
    477         // If the surfaces are already in use, we have just changed orientation or otherwise
    478         // re-created the fragment.  In this case we need to inflate the video call views and
    479         // restore the surfaces.
    480         if (sVideoSurfacesInUse) {
    481             inflateVideoCallViews();
    482         }
    483     }
    484 
    485     @Override
    486     public void onStop() {
    487         super.onStop();
    488         Log.d(this, "onStop:");
    489     }
    490 
    491     @Override
    492     public void onPause() {
    493         super.onPause();
    494         Log.d(this, "onPause:");
    495     }
    496 
    497     @Override
    498     public void onDestroyView() {
    499         super.onDestroyView();
    500         Log.d(this, "onDestroyView:");
    501     }
    502 
    503     /**
    504      * Creates the presenter for the {@link VideoCallFragment}.
    505      * @return The presenter instance.
    506      */
    507     @Override
    508     public VideoCallPresenter createPresenter() {
    509         Log.d(this, "createPresenter");
    510         VideoCallPresenter presenter = new VideoCallPresenter();
    511         onPresenterChanged(presenter);
    512         return presenter;
    513     }
    514 
    515     /**
    516      * @return The user interface for the presenter, which is this fragment.
    517      */
    518     @Override
    519     public VideoCallPresenter.VideoCallUi getUi() {
    520         return this;
    521     }
    522 
    523     /**
    524      * Inflate video surfaces.
    525      *
    526      * @param show {@code True} if the video surfaces should be shown.
    527      */
    528     private void inflateVideoUi(boolean show) {
    529         int visibility = show ? View.VISIBLE : View.GONE;
    530         getView().setVisibility(visibility);
    531 
    532         if (show) {
    533             inflateVideoCallViews();
    534         }
    535 
    536         if (mVideoViews != null) {
    537             mVideoViews.setVisibility(visibility);
    538         }
    539     }
    540 
    541     /**
    542      * Hides and shows the incoming video view and changes the outgoing video view's state based on
    543      * whether outgoing view is enabled or not.
    544      */
    545     public void showVideoViews(boolean previewPaused, boolean showIncoming) {
    546         inflateVideoUi(true);
    547 
    548         View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo);
    549         if (incomingVideoView != null) {
    550             incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE);
    551         }
    552         if (mCameraOff != null) {
    553             mCameraOff.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
    554         }
    555         if (mPreviewPhoto != null) {
    556             mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
    557         }
    558     }
    559 
    560     /**
    561      * Hide all video views.
    562      */
    563     public void hideVideoUi() {
    564         inflateVideoUi(false);
    565     }
    566 
    567     /**
    568      * Cleans up the video telephony surfaces.  Used when the presenter indicates a change to an
    569      * audio-only state.  Since the surfaces are static, it is important to ensure they are cleaned
    570      * up promptly.
    571      */
    572     @Override
    573     public void cleanupSurfaces() {
    574         Log.d(this, "cleanupSurfaces");
    575         if (sDisplaySurface != null) {
    576             sDisplaySurface.setDoneWithSurface();
    577             sDisplaySurface = null;
    578         }
    579         if (sPreviewSurface != null) {
    580             sPreviewSurface.setDoneWithSurface();
    581             sPreviewSurface = null;
    582         }
    583         sVideoSurfacesInUse = false;
    584     }
    585 
    586     @Override
    587     public ImageView getPreviewPhotoView() {
    588         return mPreviewPhoto;
    589     }
    590 
    591     private void onPresenterChanged(VideoCallPresenter presenter) {
    592         Log.d(this, "onPresenterChanged: Presenter=" + presenter);
    593         if (sDisplaySurface != null) {
    594             sDisplaySurface.resetPresenter(presenter);;
    595         }
    596         if (sPreviewSurface != null) {
    597             sPreviewSurface.resetPresenter(presenter);
    598         }
    599     }
    600 
    601     /**
    602      * @return {@code True} if the display video surface has been created.
    603      */
    604     @Override
    605     public boolean isDisplayVideoSurfaceCreated() {
    606         boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null;
    607         Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret);
    608         return ret;
    609     }
    610 
    611     /**
    612      * @return {@code True} if the preview video surface has been created.
    613      */
    614     @Override
    615     public boolean isPreviewVideoSurfaceCreated() {
    616         boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null;
    617         Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret);
    618         return ret;
    619     }
    620 
    621     /**
    622      * {@link android.view.Surface} on which incoming video for a video call is displayed.
    623      * {@code Null} until the video views {@link android.view.ViewStub} is inflated.
    624      */
    625     @Override
    626     public Surface getDisplayVideoSurface() {
    627         return sDisplaySurface == null ? null : sDisplaySurface.getSurface();
    628     }
    629 
    630     /**
    631      * {@link android.view.Surface} on which a preview of the outgoing video for a video call is
    632      * displayed.  {@code Null} until the video views {@link android.view.ViewStub} is inflated.
    633      */
    634     @Override
    635     public Surface getPreviewVideoSurface() {
    636         return sPreviewSurface == null ? null : sPreviewSurface.getSurface();
    637     }
    638 
    639     /**
    640      * Changes the dimensions of the preview surface.  Called when the dimensions change due to a
    641      * device orientation change.
    642      *
    643      * @param width The new width.
    644      * @param height The new height.
    645      */
    646     @Override
    647     public void setPreviewSize(int width, int height) {
    648         Log.d(this, "setPreviewSize: width=" + width + " height=" + height);
    649         if (sPreviewSurface != null) {
    650             TextureView preview = sPreviewSurface.getTextureView();
    651 
    652             if (preview == null ) {
    653                 return;
    654             }
    655 
    656             // Set the dimensions of both the video surface and the FrameLayout containing it.
    657             ViewGroup.LayoutParams params = preview.getLayoutParams();
    658             params.width = width;
    659             params.height = height;
    660             preview.setLayoutParams(params);
    661 
    662             if (mPreviewVideoContainer != null) {
    663                 ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams();
    664                 containerParams.width = width;
    665                 containerParams.height = height;
    666                 mPreviewVideoContainer.setLayoutParams(containerParams);
    667             }
    668 
    669             // The width and height are interchanged outside of this method based on the current
    670             // orientation, so we can transform using "width", which will be either the width or
    671             // the height.
    672             Matrix transform = new Matrix();
    673             transform.setScale(-1, 1, width/2, 0);
    674             preview.setTransform(transform);
    675         }
    676     }
    677 
    678     @Override
    679     public void setPreviewSurfaceSize(int width, int height) {
    680         final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
    681         Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height +
    682                 " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable);
    683         if (isPreviewSurfaceAvailable) {
    684             sPreviewSurface.setSurfaceDimensions(width, height);
    685         }
    686     }
    687 
    688     /**
    689      * returns UI's current orientation.
    690      */
    691     @Override
    692     public int getCurrentRotation() {
    693         try {
    694             return getActivity().getWindowManager().getDefaultDisplay().getRotation();
    695         } catch (Exception e) {
    696             Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e);
    697         }
    698         return ORIENTATION_UNKNOWN;
    699     }
    700 
    701     /**
    702      * Changes the dimensions of the display video surface. Called when the dimensions change due to
    703      * a peer resolution update
    704      *
    705      * @param width The new width.
    706      * @param height The new height.
    707      */
    708     @Override
    709     public void setDisplayVideoSize(int width, int height) {
    710         Log.d(this, "setDisplayVideoSize: width=" + width + " height=" + height);
    711         if (sDisplaySurface != null) {
    712             TextureView displayVideo = sDisplaySurface.getTextureView();
    713             if (displayVideo == null) {
    714                 Log.e(this, "Display Video texture view is null. Bail out");
    715                 return;
    716             }
    717             sDisplaySize = new Point(width, height);
    718             setSurfaceSizeAndTranslation(displayVideo, sDisplaySize);
    719         } else {
    720             Log.e(this, "Display Video Surface is null. Bail out");
    721         }
    722     }
    723 
    724     /**
    725      * Determines the size of the device screen.
    726      *
    727      * @return {@link Point} specifying the width and height of the screen.
    728      */
    729     @Override
    730     public Point getScreenSize() {
    731         // Get current screen size.
    732         Display display = getActivity().getWindowManager().getDefaultDisplay();
    733         Point size = new Point();
    734         display.getSize(size);
    735 
    736         return size;
    737     }
    738 
    739     /**
    740      * Determines the size of the preview surface.
    741      *
    742      * @return {@link Point} specifying the width and height of the preview surface.
    743      */
    744     @Override
    745     public Point getPreviewSize() {
    746         if (sPreviewSurface == null) {
    747             return null;
    748         }
    749         return sPreviewSurface.getSurfaceDimensions();
    750     }
    751 
    752     /**
    753      * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
    754      * and creates {@link VideoCallSurface} instances to track the surfaces.
    755      */
    756     private void inflateVideoCallViews() {
    757         Log.d(this, "inflateVideoCallViews");
    758         if (mVideoViews == null ) {
    759             mVideoViews = mVideoViewsStub.inflate();
    760         }
    761 
    762         if (mVideoViews != null) {
    763             mPreviewVideoContainer = mVideoViews.findViewById(R.id.previewVideoContainer);
    764             mCameraOff = mVideoViews.findViewById(R.id.previewCameraOff);
    765             mPreviewPhoto = (ImageView) mVideoViews.findViewById(R.id.previewProfilePhoto);
    766 
    767             TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);
    768 
    769             Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
    770             //If peer adjusted screen size is not available, set screen size to default display size
    771             Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize;
    772             setSurfaceSizeAndTranslation(displaySurface, screenSize);
    773 
    774             if (!sVideoSurfacesInUse) {
    775                 // Where the video surfaces are not already in use (first time creating them),
    776                 // setup new VideoCallSurface instances to track them.
    777                 Log.d(this, " inflateVideoCallViews screenSize" + screenSize);
    778 
    779                 sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY,
    780                         (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
    781                         screenSize.y);
    782                 sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW,
    783                         (TextureView) mVideoViews.findViewById(R.id.previewVideo));
    784                 sVideoSurfacesInUse = true;
    785             } else {
    786                 // In this case, the video surfaces are already in use (we are recreating the
    787                 // Fragment after a destroy/create cycle resulting from a rotation.
    788                 sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById(
    789                         R.id.incomingVideo));
    790                 sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById(
    791                         R.id.previewVideo));
    792             }
    793 
    794             // Attempt to center the incoming video view, if it is in the layout.
    795             final ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
    796             observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    797                 @Override
    798                 public void onGlobalLayout() {
    799                     // Check if the layout includes the incoming video surface -- this will only be the
    800                     // case for a video call.
    801                     View displayVideo = mVideoViews.findViewById(R.id.incomingVideo);
    802                     if (displayVideo != null) {
    803                         centerDisplayView(displayVideo);
    804                     }
    805                     mIsLayoutComplete = true;
    806 
    807                     // Remove the listener so we don't continually re-layout.
    808                     ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
    809                     if (observer.isAlive()) {
    810                         observer.removeOnGlobalLayoutListener(this);
    811                     }
    812                 }
    813             });
    814         }
    815     }
    816 
    817     /**
    818      * Resizes a surface so that it has the same size as the full screen and so that it is
    819      * centered vertically below the call card.
    820      *
    821      * @param textureView The {@link TextureView} to resize and position.
    822      * @param size The size of the screen.
    823      */
    824     private void setSurfaceSizeAndTranslation(TextureView textureView, Point size) {
    825         // Set the surface to have that size.
    826         ViewGroup.LayoutParams params = textureView.getLayoutParams();
    827         params.width = size.x;
    828         params.height = size.y;
    829         textureView.setLayoutParams(params);
    830         Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" +
    831                 mIsLayoutComplete + "IsLandscape=" + mIsLandscape);
    832 
    833         // It is only possible to center the display view if layout of the views has completed.
    834         // It is only after layout is complete that the dimensions of the Call Card has been
    835         // established, which is a prerequisite to centering the view.
    836         // Incoming video calls will center the view
    837         if (mIsLayoutComplete) {
    838             centerDisplayView(textureView);
    839         }
    840     }
    841 }
    842