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