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          */
VideoCallSurface(VideoCallPresenter presenter, int surfaceId, TextureView textureView)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          */
VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView, int width, int height)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          */
recreateView(TextureView view)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 
resetPresenter(VideoCallPresenter presenter)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
onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height)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 
onSurfaceCreated()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
onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height)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
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)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 
onSurfaceDestroyed()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
onSurfaceTextureUpdated(SurfaceTexture surface)298         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
299             // Not Handled
300         }
301 
302         @Override
onViewAttachedToWindow(View v)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
onViewDetachedFromWindow(View v)313         public void onViewDetachedFromWindow(View v) {}
314 
315         /**
316          * Retrieves the current {@link TextureView}.
317          *
318          * @return The {@link TextureView}.
319          */
getTextureView()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          */
setDoneWithSurface()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 
onSurfaceReleased()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          */
getSurface()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          */
setSurfaceDimensions(int width, int height)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          */
createSurface(int width, int height)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
onClick(View view)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          */
getSurfaceDimensions()419         public Point getSurfaceDimensions() {
420             return new Point(mWidth, mHeight);
421         }
422     };
423 
424     @Override
onCreate(Bundle savedInstanceState)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
onActivityCreated(Bundle savedInstanceState)437     public void onActivityCreated(Bundle savedInstanceState) {
438         super.onActivityCreated(savedInstanceState);
439 
440         mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape);
441 
442         Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
443         getPresenter().init(getActivity());
444     }
445 
446     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)447     public View onCreateView(LayoutInflater inflater, ViewGroup container,
448             Bundle savedInstanceState) {
449         super.onCreateView(inflater, container, savedInstanceState);
450 
451         final View view = inflater.inflate(R.layout.video_call_fragment, container, false);
452 
453         return view;
454     }
455 
456     /**
457      * Centers the display view vertically for portrait orientations. The view is centered within
458      * the available space not occupied by the call card. This is a no-op for landscape mode.
459      *
460      * @param displayVideo The video view to center.
461      */
centerDisplayView(View displayVideo)462     private void centerDisplayView(View displayVideo) {
463         if (!mIsLandscape) {
464             ViewGroup.LayoutParams p = displayVideo.getLayoutParams();
465             int height = p.height;
466 
467             float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
468             // If space beside call card is zeo, layout hasn't happened yet so there is no point
469             // in attempting to center the view.
470             if (Math.abs(spaceBesideCallCard - 0.0f) < 0.0001) {
471                 return;
472             }
473             float videoViewTranslation = height / 2  - spaceBesideCallCard / 2;
474             displayVideo.setTranslationY(videoViewTranslation);
475         }
476     }
477 
478     /**
479      * After creation of the fragment view, retrieves the required views.
480      *
481      * @param view The fragment view.
482      * @param savedInstanceState The saved instance state.
483      */
484     @Override
onViewCreated(View view, Bundle savedInstanceState)485     public void onViewCreated(View view, Bundle savedInstanceState) {
486         super.onViewCreated(view, savedInstanceState);
487         Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse);
488 
489         mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
490     }
491 
492     @Override
onStop()493     public void onStop() {
494         super.onStop();
495         Log.d(this, "onStop:");
496     }
497 
498     @Override
onPause()499     public void onPause() {
500         super.onPause();
501         Log.d(this, "onPause:");
502     }
503 
504     @Override
onDestroyView()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
createPresenter()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
getUi()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      */
inflateVideoUi(boolean show)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      */
showVideoViews(boolean previewPaused, boolean showIncoming)552     public void showVideoViews(boolean previewPaused, boolean showIncoming) {
553         inflateVideoUi(true);
554 
555         View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo);
556         if (incomingVideoView != null) {
557             incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE);
558         }
559         if (mCameraOff != null) {
560             mCameraOff.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
561         }
562         if (mPreviewPhoto != null) {
563             mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE);
564         }
565     }
566 
567     /**
568      * Hide all video views.
569      */
hideVideoUi()570     public void hideVideoUi() {
571         inflateVideoUi(false);
572     }
573 
574     /**
575      * Cleans up the video telephony surfaces.  Used when the presenter indicates a change to an
576      * audio-only state.  Since the surfaces are static, it is important to ensure they are cleaned
577      * up promptly.
578      */
579     @Override
cleanupSurfaces()580     public void cleanupSurfaces() {
581         Log.d(this, "cleanupSurfaces");
582         if (sDisplaySurface != null) {
583             sDisplaySurface.setDoneWithSurface();
584             sDisplaySurface = null;
585         }
586         if (sPreviewSurface != null) {
587             sPreviewSurface.setDoneWithSurface();
588             sPreviewSurface = null;
589         }
590         sVideoSurfacesInUse = false;
591     }
592 
593     @Override
getPreviewPhotoView()594     public ImageView getPreviewPhotoView() {
595         return mPreviewPhoto;
596     }
597 
598     /**
599      * Adjusts the location of the video preview view by the specified offset.
600      *
601      * @param shiftUp {@code true} if the preview should shift up, {@code false} if it should shift
602      *      down.
603      * @param offset The offset.
604      */
605     @Override
adjustPreviewLocation(boolean shiftUp, int offset)606     public void adjustPreviewLocation(boolean shiftUp, int offset) {
607         if (sPreviewSurface == null || mPreviewVideoContainer == null) {
608             return;
609         }
610 
611         // Set the position of the secondary call info card to its starting location.
612         mPreviewVideoContainer.setTranslationY(shiftUp ? 0 : -offset);
613 
614         // Animate the secondary card info slide up/down as it appears and disappears.
615         mPreviewVideoContainer.animate()
616                 .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
617                 .setDuration(mAnimationDuration)
618                 .translationY(shiftUp ? -offset : 0)
619                 .start();
620     }
621 
onPresenterChanged(VideoCallPresenter presenter)622     private void onPresenterChanged(VideoCallPresenter presenter) {
623         Log.d(this, "onPresenterChanged: Presenter=" + presenter);
624         if (sDisplaySurface != null) {
625             sDisplaySurface.resetPresenter(presenter);;
626         }
627         if (sPreviewSurface != null) {
628             sPreviewSurface.resetPresenter(presenter);
629         }
630     }
631 
632     /**
633      * @return {@code True} if the display video surface has been created.
634      */
635     @Override
isDisplayVideoSurfaceCreated()636     public boolean isDisplayVideoSurfaceCreated() {
637         boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null;
638         Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret);
639         return ret;
640     }
641 
642     /**
643      * @return {@code True} if the preview video surface has been created.
644      */
645     @Override
isPreviewVideoSurfaceCreated()646     public boolean isPreviewVideoSurfaceCreated() {
647         boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null;
648         Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret);
649         return ret;
650     }
651 
652     /**
653      * {@link android.view.Surface} on which incoming video for a video call is displayed.
654      * {@code Null} until the video views {@link android.view.ViewStub} is inflated.
655      */
656     @Override
getDisplayVideoSurface()657     public Surface getDisplayVideoSurface() {
658         return sDisplaySurface == null ? null : sDisplaySurface.getSurface();
659     }
660 
661     /**
662      * {@link android.view.Surface} on which a preview of the outgoing video for a video call is
663      * displayed.  {@code Null} until the video views {@link android.view.ViewStub} is inflated.
664      */
665     @Override
getPreviewVideoSurface()666     public Surface getPreviewVideoSurface() {
667         return sPreviewSurface == null ? null : sPreviewSurface.getSurface();
668     }
669 
670     /**
671      * Changes the dimensions of the preview surface.  Called when the dimensions change due to a
672      * device orientation change.
673      *
674      * @param width The new width.
675      * @param height The new height.
676      */
677     @Override
setPreviewSize(int width, int height)678     public void setPreviewSize(int width, int height) {
679         Log.d(this, "setPreviewSize: width=" + width + " height=" + height);
680         if (sPreviewSurface != null) {
681             TextureView preview = sPreviewSurface.getTextureView();
682 
683             if (preview == null ) {
684                 return;
685             }
686 
687             // Set the dimensions of both the video surface and the FrameLayout containing it.
688             ViewGroup.LayoutParams params = preview.getLayoutParams();
689             params.width = width;
690             params.height = height;
691             preview.setLayoutParams(params);
692 
693             if (mPreviewVideoContainer != null) {
694                 ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams();
695                 containerParams.width = width;
696                 containerParams.height = height;
697                 mPreviewVideoContainer.setLayoutParams(containerParams);
698             }
699 
700             // The width and height are interchanged outside of this method based on the current
701             // orientation, so we can transform using "width", which will be either the width or
702             // the height.
703             Matrix transform = new Matrix();
704             transform.setScale(-1, 1, width/2, 0);
705             preview.setTransform(transform);
706         }
707     }
708 
709     /**
710      * Sets the rotation of the preview surface.  Called when the dimensions change due to a
711      * device orientation change.
712      *
713      * Please note that the screen orientation passed in is subtracted from 360 to get the actual
714      * preview rotation values.
715      *
716      * @param rotation The screen orientation. One of -
717      * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
718      * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
719      * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
720      * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
721      */
722     @Override
setPreviewRotation(int orientation)723     public void setPreviewRotation(int orientation) {
724         Log.d(this, "setPreviewRotation: orientation=" + orientation);
725         if (sPreviewSurface != null) {
726             TextureView preview = sPreviewSurface.getTextureView();
727 
728             if (preview == null ) {
729                 return;
730             }
731 
732             preview.setRotation(orientation);
733         }
734     }
735 
736     @Override
setPreviewSurfaceSize(int width, int height)737     public void setPreviewSurfaceSize(int width, int height) {
738         final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
739         Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height +
740                 " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable);
741         if (isPreviewSurfaceAvailable) {
742             sPreviewSurface.setSurfaceDimensions(width, height);
743         }
744     }
745 
746     /**
747      * returns UI's current orientation.
748      */
749     @Override
getCurrentRotation()750     public int getCurrentRotation() {
751         try {
752             return getActivity().getWindowManager().getDefaultDisplay().getRotation();
753         } catch (Exception e) {
754             Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e);
755         }
756         return ORIENTATION_UNKNOWN;
757     }
758 
759     /**
760      * Changes the dimensions of the display video surface. Called when the dimensions change due to
761      * a peer resolution update
762      *
763      * @param width The new width.
764      * @param height The new height.
765      */
766     @Override
setDisplayVideoSize(int width, int height)767     public void setDisplayVideoSize(int width, int height) {
768         Log.v(this, "setDisplayVideoSize: width=" + width + " height=" + height);
769         if (sDisplaySurface != null) {
770             TextureView displayVideo = sDisplaySurface.getTextureView();
771             if (displayVideo == null) {
772                 Log.e(this, "Display Video texture view is null. Bail out");
773                 return;
774             }
775             sDisplaySize = new Point(width, height);
776             setSurfaceSizeAndTranslation(displayVideo, sDisplaySize);
777         } else {
778             Log.e(this, "Display Video Surface is null. Bail out");
779         }
780     }
781 
782     /**
783      * Determines the size of the device screen.
784      *
785      * @return {@link Point} specifying the width and height of the screen.
786      */
787     @Override
getScreenSize()788     public Point getScreenSize() {
789         // Get current screen size.
790         Display display = getActivity().getWindowManager().getDefaultDisplay();
791         Point size = new Point();
792         display.getSize(size);
793 
794         return size;
795     }
796 
797     /**
798      * Determines the size of the preview surface.
799      *
800      * @return {@link Point} specifying the width and height of the preview surface.
801      */
802     @Override
getPreviewSize()803     public Point getPreviewSize() {
804         if (sPreviewSurface == null) {
805             return null;
806         }
807         return sPreviewSurface.getSurfaceDimensions();
808     }
809 
810     /**
811      * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
812      * and creates {@link VideoCallSurface} instances to track the surfaces.
813      */
inflateVideoCallViews()814     private void inflateVideoCallViews() {
815         Log.d(this, "inflateVideoCallViews");
816         if (mVideoViews == null ) {
817             mVideoViews = mVideoViewsStub.inflate();
818         }
819 
820         if (mVideoViews != null) {
821             mPreviewVideoContainer = mVideoViews.findViewById(R.id.previewVideoContainer);
822             mCameraOff = mVideoViews.findViewById(R.id.previewCameraOff);
823             mPreviewPhoto = (ImageView) mVideoViews.findViewById(R.id.previewProfilePhoto);
824 
825             TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);
826 
827             Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
828             //If peer adjusted screen size is not available, set screen size to default display size
829             Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize;
830             setSurfaceSizeAndTranslation(displaySurface, screenSize);
831 
832             if (!sVideoSurfacesInUse) {
833                 // Where the video surfaces are not already in use (first time creating them),
834                 // setup new VideoCallSurface instances to track them.
835                 Log.d(this, " inflateVideoCallViews screenSize" + screenSize);
836 
837                 sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY,
838                         (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
839                         screenSize.y);
840                 sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW,
841                         (TextureView) mVideoViews.findViewById(R.id.previewVideo));
842                 sVideoSurfacesInUse = true;
843             } else {
844                 // In this case, the video surfaces are already in use (we are recreating the
845                 // Fragment after a destroy/create cycle resulting from a rotation.
846                 sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById(
847                         R.id.incomingVideo));
848                 sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById(
849                         R.id.previewVideo));
850             }
851 
852             // Attempt to center the incoming video view, if it is in the layout.
853             final ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
854             observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
855                 @Override
856                 public void onGlobalLayout() {
857                     // Check if the layout includes the incoming video surface -- this will only be the
858                     // case for a video call.
859                     View displayVideo = mVideoViews.findViewById(R.id.incomingVideo);
860                     if (displayVideo != null) {
861                         centerDisplayView(displayVideo);
862                     }
863                     mIsLayoutComplete = true;
864 
865                     // Remove the listener so we don't continually re-layout.
866                     ViewTreeObserver observer = mVideoViews.getViewTreeObserver();
867                     if (observer.isAlive()) {
868                         observer.removeOnGlobalLayoutListener(this);
869                     }
870                 }
871             });
872         }
873     }
874 
875     /**
876      * Resizes a surface so that it has the same size as the full screen and so that it is
877      * centered vertically below the call card.
878      *
879      * @param textureView The {@link TextureView} to resize and position.
880      * @param size The size of the screen.
881      */
setSurfaceSizeAndTranslation(TextureView textureView, Point size)882     private void setSurfaceSizeAndTranslation(TextureView textureView, Point size) {
883         // Set the surface to have that size.
884         ViewGroup.LayoutParams params = textureView.getLayoutParams();
885         params.width = size.x;
886         params.height = size.y;
887         textureView.setLayoutParams(params);
888         Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" +
889                 mIsLayoutComplete + "IsLandscape=" + mIsLandscape);
890 
891         // It is only possible to center the display view if layout of the views has completed.
892         // It is only after layout is complete that the dimensions of the Call Card has been
893         // established, which is a prerequisite to centering the view.
894         // Incoming video calls will center the view
895         if (mIsLayoutComplete) {
896             centerDisplayView(textureView);
897         }
898     }
899 }
900