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
17package com.android.incallui;
18
19import android.graphics.Matrix;
20import android.graphics.Point;
21import android.graphics.SurfaceTexture;
22import android.os.Bundle;
23import android.view.Display;
24import android.view.LayoutInflater;
25import android.view.Surface;
26import android.view.TextureView;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewStub;
30import android.view.ViewTreeObserver;
31import android.widget.FrameLayout;
32import android.widget.ImageView;
33
34import com.google.common.base.Objects;
35
36/**
37 * Fragment containing video calling surfaces.
38 */
39public 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