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