• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
VideoCallSurface(VideoCallPresenter presenter, int surfaceId, TextureView textureView)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          */
VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView, int width, int height)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          */
recreateView(TextureView view)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 
resetPresenter(VideoCallPresenter presenter)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
onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height)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 
onSurfaceCreated()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
onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height)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
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)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 
onSurfaceDestroyed()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
onSurfaceTextureUpdated(SurfaceTexture surface)294         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
295             // Not Handled
296         }
297 
298         @Override
onViewAttachedToWindow(View v)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
onViewDetachedFromWindow(View v)309         public void onViewDetachedFromWindow(View v) {}
310 
311         /**
312          * Retrieves the current {@link TextureView}.
313          *
314          * @return The {@link TextureView}.
315          */
getTextureView()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          */
setDoneWithSurface()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 
onSurfaceReleased()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          */
getSurface()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          */
setSurfaceDimensions(int width, int height)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          */
createSurface(int width, int height)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
onClick(View view)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          */
getSurfaceDimensions()414         public Point getSurfaceDimensions() {
415             return new Point(mWidth, mHeight);
416         }
417     };
418 
419     @Override
onCreate(Bundle savedInstanceState)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
onActivityCreated(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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      */
centerDisplayView(View displayVideo)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
onViewCreated(View view, Bundle savedInstanceState)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
onStop()486     public void onStop() {
487         super.onStop();
488         Log.d(this, "onStop:");
489     }
490 
491     @Override
onPause()492     public void onPause() {
493         super.onPause();
494         Log.d(this, "onPause:");
495     }
496 
497     @Override
onDestroyView()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
createPresenter()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
getUi()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      */
inflateVideoUi(boolean show)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      */
showVideoViews(boolean previewPaused, boolean showIncoming)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      */
hideVideoUi()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
cleanupSurfaces()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
getPreviewPhotoView()587     public ImageView getPreviewPhotoView() {
588         return mPreviewPhoto;
589     }
590 
onPresenterChanged(VideoCallPresenter presenter)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
isDisplayVideoSurfaceCreated()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
isPreviewVideoSurfaceCreated()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
getDisplayVideoSurface()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
getPreviewVideoSurface()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
setPreviewSize(int width, int height)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
setPreviewSurfaceSize(int width, int height)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
getCurrentRotation()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
setDisplayVideoSize(int width, int height)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
getScreenSize()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
getPreviewSize()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      */
inflateVideoCallViews()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      */
setSurfaceSizeAndTranslation(TextureView textureView, Point size)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