• 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.content.res.Configuration;
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 
32 /**
33  * Fragment containing video calling surfaces.
34  */
35 public class VideoCallFragment extends BaseFragment<VideoCallPresenter,
36         VideoCallPresenter.VideoCallUi> implements VideoCallPresenter.VideoCallUi {
37 
38     /**
39      * Used to indicate that the surface dimensions are not set.
40      */
41     private static final int DIMENSIONS_NOT_SET = -1;
42 
43     /**
44      * Surface ID for the display surface.
45      */
46     public static final int SURFACE_DISPLAY = 1;
47 
48     /**
49      * Surface ID for the preview surface.
50      */
51     public static final int SURFACE_PREVIEW = 2;
52 
53     // Static storage used to retain the video surfaces across Activity restart.
54     // TextureViews are not parcelable, so it is not possible to store them in the saved state.
55     private static boolean sVideoSurfacesInUse = false;
56     private static VideoCallSurface sPreviewSurface = null;
57     private static VideoCallSurface sDisplaySurface = null;
58 
59     /**
60      * {@link ViewStub} holding the video call surfaces.  This is the parent for the
61      * {@link VideoCallFragment}.  Used to ensure that the video surfaces are only inflated when
62      * required.
63      */
64     private ViewStub mVideoViewsStub;
65 
66     /**
67      * Inflated view containing the video call surfaces represented by the {@link ViewStub}.
68      */
69     private View mVideoViews;
70 
71     /**
72      * {@code True} when the entering the activity again after a restart due to orientation change.
73      */
74     private boolean mIsActivityRestart;
75 
76     /**
77      * {@code True} when the layout of the activity has been completed.
78      */
79     private boolean mIsLayoutComplete = false;
80 
81     /**
82      * {@code True} if in landscape mode.
83      */
84     private boolean mIsLandscape;
85 
86     /**
87      * The width of the surface.
88      */
89     private int mWidth = DIMENSIONS_NOT_SET;
90 
91     /**
92      * The height of the surface.
93      */
94     private int mHeight = DIMENSIONS_NOT_SET;
95 
96     /**
97      * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and
98      * {@link Surface}.  Used to manage the lifecycle of these objects across device orientation
99      * changes.
100      */
101     private class VideoCallSurface implements TextureView.SurfaceTextureListener,
102             View.OnClickListener {
103         private int mSurfaceId;
104         private TextureView mTextureView;
105         private SurfaceTexture mSavedSurfaceTexture;
106         private Surface mSavedSurface;
107 
108         /**
109          * Creates an instance of a {@link VideoCallSurface}.
110          *
111          * @param surfaceId The surface ID of the surface.
112          * @param textureView The {@link TextureView} for the surface.
113          */
VideoCallSurface(int surfaceId, TextureView textureView)114         public VideoCallSurface(int surfaceId, TextureView textureView) {
115             this(surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
116         }
117 
118         /**
119          * Creates an instance of a {@link VideoCallSurface}.
120          *
121          * @param surfaceId The surface ID of the surface.
122          * @param textureView The {@link TextureView} for the surface.
123          * @param width The width of the surface.
124          * @param height The height of the surface.
125          */
VideoCallSurface(int surfaceId, TextureView textureView, int width, int height)126         public VideoCallSurface(int surfaceId, TextureView textureView, int width, int height) {
127             mWidth = width;
128             mHeight = height;
129             mSurfaceId = surfaceId;
130 
131             recreateView(textureView);
132         }
133 
134         /**
135          * Recreates a {@link VideoCallSurface} after a device orientation change.  Re-applies the
136          * saved {@link SurfaceTexture} to the
137          *
138          * @param view The {@link TextureView}.
139          */
recreateView(TextureView view)140         public void recreateView(TextureView view) {
141             mTextureView = view;
142             mTextureView.setSurfaceTextureListener(this);
143             mTextureView.setOnClickListener(this);
144 
145             if (mSavedSurfaceTexture != null) {
146                 mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
147             }
148         }
149 
150         /**
151          * Handles {@link SurfaceTexture} callback to indicate that a {@link SurfaceTexture} has
152          * been successfully created.
153          *
154          * @param surfaceTexture The {@link SurfaceTexture} which has been created.
155          * @param width The width of the {@link SurfaceTexture}.
156          * @param height The height of the {@link SurfaceTexture}.
157          */
158         @Override
onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height)159         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width,
160                 int height) {
161             boolean surfaceCreated;
162             // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
163             // If a saved {@link SurfaceTexture} is available, we are re-creating after an
164             // orientation change.
165             if (mSavedSurfaceTexture == null) {
166                 mSavedSurfaceTexture = surfaceTexture;
167                 surfaceCreated = createSurface();
168             } else {
169                 // A saved SurfaceTexture was found.
170                 surfaceCreated = true;
171             }
172 
173             // Inform presenter that the surface is available.
174             if (surfaceCreated) {
175                 getPresenter().onSurfaceCreated(mSurfaceId);
176             }
177         }
178 
179         /**
180          * Handles a change in the {@link SurfaceTexture}'s size.
181          *
182          * @param surfaceTexture The {@link SurfaceTexture}.
183          * @param width The new width.
184          * @param height The new height.
185          */
186         @Override
onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height)187         public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width,
188                 int height) {
189             // Not handled
190         }
191 
192         /**
193          * Handles {@link SurfaceTexture} destruct callback, indicating that it has been destroyed.
194          *
195          * @param surfaceTexture The {@link SurfaceTexture}.
196          * @return {@code True} if the {@link TextureView} can release the {@link SurfaceTexture}.
197          */
198         @Override
onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)199         public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
200             /**
201              * Destroying the surface texture; inform the presenter so it can null the surfaces.
202              */
203             if (mSavedSurfaceTexture == null) {
204                 getPresenter().onSurfaceDestroyed(mSurfaceId);
205                 if (mSavedSurface != null) {
206                     mSavedSurface.release();
207                     mSavedSurface = null;
208                 }
209             }
210 
211             // The saved SurfaceTexture will be null if we're shutting down, so we want to
212             // return "true" in that case (indicating that TextureView can release the ST).
213             return (mSavedSurfaceTexture == null);
214         }
215 
216         /**
217          * Handles {@link SurfaceTexture} update callback.
218          * @param surface
219          */
220         @Override
onSurfaceTextureUpdated(SurfaceTexture surface)221         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
222             // Not Handled
223         }
224 
225         /**
226          * Retrieves the current {@link TextureView}.
227          *
228          * @return The {@link TextureView}.
229          */
getTextureView()230         public TextureView getTextureView() {
231             return mTextureView;
232         }
233 
234         /**
235          * Called by the user presenter to indicate that the surface is no longer required due to a
236          * change in video state.  Releases and clears out the saved surface and surface textures.
237          */
setDoneWithSurface()238         public void setDoneWithSurface() {
239             if (mSavedSurface != null) {
240                 mSavedSurface.release();
241                 mSavedSurface = null;
242             }
243             if (mSavedSurfaceTexture != null) {
244                 mSavedSurfaceTexture.release();
245                 mSavedSurfaceTexture = null;
246             }
247         }
248 
249         /**
250          * Retrieves the saved surface instance.
251          *
252          * @return The surface.
253          */
getSurface()254         public Surface getSurface() {
255             return mSavedSurface;
256         }
257 
258         /**
259          * Sets the dimensions of the surface.
260          *
261          * @param width The width of the surface, in pixels.
262          * @param height The height of the surface, in pixels.
263          */
setSurfaceDimensions(int width, int height)264         public void setSurfaceDimensions(int width, int height) {
265             mWidth = width;
266             mHeight = height;
267 
268             if (mSavedSurfaceTexture != null) {
269                 createSurface();
270             }
271         }
272 
273         /**
274          * Creates the {@link Surface}, adjusting the {@link SurfaceTexture} buffer size.
275          */
createSurface()276         private boolean createSurface() {
277             if (mWidth != DIMENSIONS_NOT_SET && mHeight != DIMENSIONS_NOT_SET &&
278                     mSavedSurfaceTexture != null) {
279 
280                 mSavedSurfaceTexture.setDefaultBufferSize(mWidth, mHeight);
281                 mSavedSurface = new Surface(mSavedSurfaceTexture);
282                 return true;
283             }
284             return false;
285         }
286 
287         /**
288          * Handles a user clicking the surface, which is the trigger to toggle the full screen
289          * Video UI.
290          *
291          * @param view The view receiving the click.
292          */
293         @Override
onClick(View view)294         public void onClick(View view) {
295             getPresenter().onSurfaceClick(mSurfaceId);
296         }
297     };
298 
299     @Override
onCreate(Bundle savedInstanceState)300     public void onCreate(Bundle savedInstanceState) {
301         super.onCreate(savedInstanceState);
302         mIsActivityRestart = sVideoSurfacesInUse;
303     }
304 
305     /**
306      * Handles creation of the activity and initialization of the presenter.
307      *
308      * @param savedInstanceState The saved instance state.
309      */
310     @Override
onActivityCreated(Bundle savedInstanceState)311     public void onActivityCreated(Bundle savedInstanceState) {
312         super.onActivityCreated(savedInstanceState);
313 
314         mIsLandscape = getResources().getConfiguration().orientation
315                 == Configuration.ORIENTATION_LANDSCAPE;
316 
317         getPresenter().init(getActivity());
318     }
319 
320     /**
321      * Handles creation of the fragment view.
322      *
323      * @param inflater The inflater.
324      * @param container The view group containing the fragment.
325      * @param savedInstanceState The saved instance state.
326      * @return
327      */
328     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)329     public View onCreateView(LayoutInflater inflater, ViewGroup container,
330             Bundle savedInstanceState) {
331 
332         super.onCreateView(inflater, container, savedInstanceState);
333 
334         final View view = inflater.inflate(R.layout.video_call_fragment, container, false);
335 
336         // Attempt to center the incoming video view, if it is in the layout.
337         final ViewTreeObserver observer = view.getViewTreeObserver();
338         observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
339             @Override
340             public void onGlobalLayout() {
341                 // Check if the layout includes the incoming video surface -- this will only be the
342                 // case for a video call.
343                 View displayVideo = view.findViewById(R.id.incomingVideo);
344                 if (displayVideo != null) {
345                     centerDisplayView(displayVideo);
346                 }
347 
348                 mIsLayoutComplete = true;
349 
350                 // Remove the listener so we don't continually re-layout.
351                 ViewTreeObserver observer = view.getViewTreeObserver();
352                 if (observer.isAlive()) {
353                     observer.removeOnGlobalLayoutListener(this);
354                 }
355             }
356         });
357 
358         return view;
359     }
360 
361     /**
362      * Centers the display view vertically for portrait orientation, and horizontally for
363      * lanscape orientations.  The view is centered within the available space not occupied by
364      * the call card.
365      *
366      * @param displayVideo The video view to center.
367      */
centerDisplayView(View displayVideo)368     private void centerDisplayView(View displayVideo) {
369         // In a lansdcape layout we need to ensure we horizontally center the view based on whether
370         // the layout is left-to-right or right-to-left.
371         // In a left-to-right locale, the space for the video view is to the right of the call card
372         // so we need to translate it in the +X direction.
373         // In a right-to-left locale, the space for the video view is to the left of the call card
374         // so we need to translate it in the -X direction.
375         final boolean isLayoutRtl = InCallPresenter.isRtl();
376 
377         float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
378         if (mIsLandscape) {
379             float videoViewTranslation = displayVideo.getWidth() / 2
380                     - spaceBesideCallCard / 2;
381             if (isLayoutRtl) {
382                 displayVideo.setTranslationX(-videoViewTranslation);
383             } else {
384                 displayVideo.setTranslationX(videoViewTranslation);
385             }
386         } else {
387             float videoViewTranslation = displayVideo.getHeight() / 2
388                     - spaceBesideCallCard / 2;
389             displayVideo.setTranslationY(videoViewTranslation);
390         }
391     }
392 
393     /**
394      * After creation of the fragment view, retrieves the required views.
395      *
396      * @param view The fragment view.
397      * @param savedInstanceState The saved instance state.
398      */
399     @Override
onViewCreated(View view, Bundle savedInstanceState)400     public void onViewCreated(View view, Bundle savedInstanceState) {
401         super.onViewCreated(view, savedInstanceState);
402 
403         mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
404 
405         // If the surfaces are already in use, we have just changed orientation or otherwise
406         // re-created the fragment.  In this case we need to inflate the video call views and
407         // restore the surfaces.
408         if (sVideoSurfacesInUse) {
409             inflateVideoCallViews();
410         }
411     }
412 
413     /**
414      * Creates the presenter for the {@link VideoCallFragment}.
415      * @return The presenter instance.
416      */
417     @Override
createPresenter()418     public VideoCallPresenter createPresenter() {
419         return new VideoCallPresenter();
420     }
421 
422     /**
423      * @return The user interface for the presenter, which is this fragment.
424      */
425     @Override
getUi()426     VideoCallPresenter.VideoCallUi getUi() {
427         return this;
428     }
429 
430     /**
431      * Toggles visibility of the video UI.
432      *
433      * @param show {@code True} if the video surfaces should be shown.
434      */
435     @Override
showVideoUi(boolean show)436     public void showVideoUi(boolean show) {
437         int visibility = show ? View.VISIBLE : View.GONE;
438         getView().setVisibility(visibility);
439 
440         if (show) {
441             inflateVideoCallViews();
442         } else {
443             cleanupSurfaces();
444         }
445 
446         if (mVideoViews != null ) {
447             mVideoViews.setVisibility(visibility);
448         }
449     }
450 
451     /**
452      * Cleans up the video telephony surfaces.  Used when the presenter indicates a change to an
453      * audio-only state.  Since the surfaces are static, it is important to ensure they are cleaned
454      * up promptly.
455      */
456     @Override
cleanupSurfaces()457     public void cleanupSurfaces() {
458         if (sDisplaySurface != null) {
459             sDisplaySurface.setDoneWithSurface();
460             sDisplaySurface = null;
461         }
462         if (sPreviewSurface != null) {
463             sPreviewSurface.setDoneWithSurface();
464             sPreviewSurface = null;
465         }
466         sVideoSurfacesInUse = false;
467     }
468 
469     @Override
isActivityRestart()470     public boolean isActivityRestart() {
471         return mIsActivityRestart;
472     }
473 
474     /**
475      * @return {@code True} if the display video surface has been created.
476      */
477     @Override
isDisplayVideoSurfaceCreated()478     public boolean isDisplayVideoSurfaceCreated() {
479         return sDisplaySurface != null && sDisplaySurface.getSurface() != null;
480     }
481 
482     /**
483      * @return {@code True} if the preview video surface has been created.
484      */
485     @Override
isPreviewVideoSurfaceCreated()486     public boolean isPreviewVideoSurfaceCreated() {
487         return sPreviewSurface != null && sPreviewSurface.getSurface() != null;
488     }
489 
490     /**
491      * {@link android.view.Surface} on which incoming video for a video call is displayed.
492      * {@code Null} until the video views {@link android.view.ViewStub} is inflated.
493      */
494     @Override
getDisplayVideoSurface()495     public Surface getDisplayVideoSurface() {
496         return sDisplaySurface == null ? null : sDisplaySurface.getSurface();
497     }
498 
499     /**
500      * {@link android.view.Surface} on which a preview of the outgoing video for a video call is
501      * displayed.  {@code Null} until the video views {@link android.view.ViewStub} is inflated.
502      */
503     @Override
getPreviewVideoSurface()504     public Surface getPreviewVideoSurface() {
505         return sPreviewSurface == null ? null : sPreviewSurface.getSurface();
506     }
507 
508     /**
509      * Changes the dimensions of the preview surface.  Called when the dimensions change due to a
510      * device orientation change.
511      *
512      * @param width The new width.
513      * @param height The new height.
514      */
515     @Override
setPreviewSize(int width, int height)516     public void setPreviewSize(int width, int height) {
517         if (sPreviewSurface != null) {
518             TextureView preview = sPreviewSurface.getTextureView();
519 
520             if (preview == null ) {
521                 return;
522             }
523 
524             ViewGroup.LayoutParams params = preview.getLayoutParams();
525             params.width = width;
526             params.height = height;
527             preview.setLayoutParams(params);
528 
529             sPreviewSurface.setSurfaceDimensions(width, height);
530         }
531     }
532 
533     /**
534      * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
535      * and creates {@link VideoCallSurface} instances to track the surfaces.
536      */
inflateVideoCallViews()537     private void inflateVideoCallViews() {
538         if (mVideoViews == null ) {
539             mVideoViews = mVideoViewsStub.inflate();
540         }
541 
542         if (mVideoViews != null) {
543             TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);
544 
545             Point screenSize = getScreenSize();
546             setSurfaceSizeAndTranslation(displaySurface, screenSize);
547 
548             if (!sVideoSurfacesInUse) {
549                 // Where the video surfaces are not already in use (first time creating them),
550                 // setup new VideoCallSurface instances to track them.
551                 sDisplaySurface = new VideoCallSurface(SURFACE_DISPLAY,
552                         (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
553                         screenSize.y);
554                 sPreviewSurface = new VideoCallSurface(SURFACE_PREVIEW,
555                         (TextureView) mVideoViews.findViewById(R.id.previewVideo));
556                 sVideoSurfacesInUse = true;
557             } else {
558                 // In this case, the video surfaces are already in use (we are recreating the
559                 // Fragment after a destroy/create cycle resulting from a rotation.
560                 sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById(
561                         R.id.incomingVideo));
562                 sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById(
563                         R.id.previewVideo));
564             }
565         }
566     }
567 
568     /**
569      * Resizes a surface so that it has the same size as the full screen and so that it is
570      * centered vertically below the call card.
571      *
572      * @param textureView The {@link TextureView} to resize and position.
573      * @param size The size of the screen.
574      */
setSurfaceSizeAndTranslation(TextureView textureView, Point size)575     private void setSurfaceSizeAndTranslation(TextureView textureView, Point size) {
576         // Set the surface to have that size.
577         ViewGroup.LayoutParams params = textureView.getLayoutParams();
578         params.width = size.x;
579         params.height = size.y;
580         textureView.setLayoutParams(params);
581 
582         // It is only possible to center the display view if layout of the views has completed.
583         // It is only after layout is complete that the dimensions of the Call Card has been
584         // established, which is a prerequisite to centering the view.
585         // Incoming video calls will center the view
586         if (mIsLayoutComplete && ((mIsLandscape && textureView.getTranslationX() == 0) || (
587                 !mIsLandscape && textureView.getTranslationY() == 0))) {
588             centerDisplayView(textureView);
589         }
590     }
591 
592     /**
593      * Determines the size of the device screen.
594      *
595      * @return {@link Point} specifying the width and height of the screen.
596      */
getScreenSize()597     private Point getScreenSize() {
598         // Get current screen size.
599         Display display = getActivity().getWindowManager().getDefaultDisplay();
600         Point size = new Point();
601         display.getSize(size);
602 
603         return size;
604     }
605 }
606