1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.incallui; 18 19 import android.graphics.Matrix; 20 import android.graphics.Point; 21 import android.graphics.SurfaceTexture; 22 import android.os.Bundle; 23 import android.view.Display; 24 import android.view.LayoutInflater; 25 import android.view.Surface; 26 import android.view.TextureView; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.ViewStub; 30 import android.view.ViewTreeObserver; 31 import android.widget.FrameLayout; 32 import android.widget.ImageView; 33 34 import com.google.common.base.Objects; 35 36 /** 37 * Fragment containing video calling surfaces. 38 */ 39 public class VideoCallFragment extends BaseFragment<VideoCallPresenter, 40 VideoCallPresenter.VideoCallUi> implements VideoCallPresenter.VideoCallUi { 41 private static final String TAG = VideoCallFragment.class.getSimpleName(); 42 private static final boolean DEBUG = false; 43 44 /** 45 * Used to indicate that the surface dimensions are not set. 46 */ 47 private static final int DIMENSIONS_NOT_SET = -1; 48 49 /** 50 * Surface ID for the display surface. 51 */ 52 public static final int SURFACE_DISPLAY = 1; 53 54 /** 55 * Surface ID for the preview surface. 56 */ 57 public static final int SURFACE_PREVIEW = 2; 58 59 /** 60 * Used to indicate that the UI rotation is unknown. 61 */ 62 public static final int ORIENTATION_UNKNOWN = -1; 63 64 // Static storage used to retain the video surfaces across Activity restart. 65 // TextureViews are not parcelable, so it is not possible to store them in the saved state. 66 private static boolean sVideoSurfacesInUse = false; 67 private static VideoCallSurface sPreviewSurface = null; 68 private static VideoCallSurface sDisplaySurface = null; 69 private static Point sDisplaySize = null; 70 71 /** 72 * {@link ViewStub} holding the video call surfaces. This is the parent for the 73 * {@link VideoCallFragment}. Used to ensure that the video surfaces are only inflated when 74 * required. 75 */ 76 private ViewStub mVideoViewsStub; 77 78 /** 79 * Inflated view containing the video call surfaces represented by the {@link ViewStub}. 80 */ 81 private View mVideoViews; 82 83 /** 84 * The {@link FrameLayout} containing the preview surface. 85 */ 86 private View mPreviewVideoContainer; 87 88 /** 89 * Icon shown to indicate that the outgoing camera has been turned off. 90 */ 91 private View mCameraOff; 92 93 /** 94 * {@link ImageView} containing the user's profile photo. 95 */ 96 private ImageView mPreviewPhoto; 97 98 /** 99 * {@code True} when the layout of the activity has been completed. 100 */ 101 private boolean mIsLayoutComplete = false; 102 103 /** 104 * {@code True} if in landscape mode. 105 */ 106 private boolean mIsLandscape; 107 108 /** 109 * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and 110 * {@link Surface}. Used to manage the lifecycle of these objects across device orientation 111 * changes. 112 */ 113 private static class VideoCallSurface implements TextureView.SurfaceTextureListener, 114 View.OnClickListener, View.OnAttachStateChangeListener { 115 private int mSurfaceId; 116 private VideoCallPresenter mPresenter; 117 private TextureView mTextureView; 118 private SurfaceTexture mSavedSurfaceTexture; 119 private Surface mSavedSurface; 120 private boolean mIsDoneWithSurface; 121 private int mWidth = DIMENSIONS_NOT_SET; 122 private int mHeight = DIMENSIONS_NOT_SET; 123 124 /** 125 * Creates an instance of a {@link VideoCallSurface}. 126 * 127 * @param surfaceId The surface ID of the surface. 128 * @param textureView The {@link TextureView} for the surface. 129 */ 130 public VideoCallSurface(VideoCallPresenter presenter, int surfaceId, 131 TextureView textureView) { 132 this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET); 133 } 134 135 /** 136 * Creates an instance of a {@link VideoCallSurface}. 137 * 138 * @param surfaceId The surface ID of the surface. 139 * @param textureView The {@link TextureView} for the surface. 140 * @param width The width of the surface. 141 * @param height The height of the surface. 142 */ 143 public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView, 144 int width, int height) { 145 Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId + 146 " width=" + width + " height=" + height); 147 mPresenter = presenter; 148 mWidth = width; 149 mHeight = height; 150 mSurfaceId = surfaceId; 151 152 recreateView(textureView); 153 } 154 155 /** 156 * Recreates a {@link VideoCallSurface} after a device orientation change. Re-applies the 157 * saved {@link SurfaceTexture} to the 158 * 159 * @param view The {@link TextureView}. 160 */ 161 public void recreateView(TextureView view) { 162 if (DEBUG) { 163 Log.i(TAG, "recreateView: " + view); 164 } 165 166 if (mTextureView == view) { 167 return; 168 } 169 170 mTextureView = view; 171 mTextureView.setSurfaceTextureListener(this); 172 mTextureView.setOnClickListener(this); 173 174 final boolean areSameSurfaces = 175 Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture()); 176 Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture 177 + " areSameSurfaces=" + areSameSurfaces); 178 if (mSavedSurfaceTexture != null && !areSameSurfaces) { 179 mTextureView.setSurfaceTexture(mSavedSurfaceTexture); 180 if (createSurface(mWidth, mHeight)) { 181 onSurfaceCreated(); 182 } 183 } 184 mIsDoneWithSurface = false; 185 } 186 187 public void resetPresenter(VideoCallPresenter presenter) { 188 Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter=" 189 + presenter); 190 mPresenter = presenter; 191 } 192 193 /** 194 * Handles {@link SurfaceTexture} callback to indicate that a {@link SurfaceTexture} has 195 * been successfully created. 196 * 197 * @param surfaceTexture The {@link SurfaceTexture} which has been created. 198 * @param width The width of the {@link SurfaceTexture}. 199 * @param height The height of the {@link SurfaceTexture}. 200 */ 201 @Override 202 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, 203 int height) { 204 boolean surfaceCreated; 205 if (DEBUG) { 206 Log.i(TAG, "onSurfaceTextureAvailable: " + surfaceTexture); 207 } 208 // Where there is no saved {@link SurfaceTexture} available, use the newly created one. 209 // If a saved {@link SurfaceTexture} is available, we are re-creating after an 210 // orientation change. 211 Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture=" 212 + surfaceTexture + " width=" + width 213 + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture); 214 Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter); 215 if (mSavedSurfaceTexture == null) { 216 mSavedSurfaceTexture = surfaceTexture; 217 surfaceCreated = createSurface(width, height); 218 } else { 219 // A saved SurfaceTexture was found. 220 Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface..."); 221 mTextureView.setSurfaceTexture(mSavedSurfaceTexture); 222 surfaceCreated = true; 223 } 224 225 // Inform presenter that the surface is available. 226 if (surfaceCreated) { 227 onSurfaceCreated(); 228 } 229 } 230 231 private void onSurfaceCreated() { 232 if (mPresenter != null) { 233 mPresenter.onSurfaceCreated(mSurfaceId); 234 } else { 235 Log.e(this, "onSurfaceTextureAvailable: Presenter is null"); 236 } 237 } 238 239 /** 240 * Handles a change in the {@link SurfaceTexture}'s size. 241 * 242 * @param surfaceTexture The {@link SurfaceTexture}. 243 * @param width The new width. 244 * @param height The new height. 245 */ 246 @Override 247 public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, 248 int height) { 249 // Not handled 250 } 251 252 /** 253 * Handles {@link SurfaceTexture} destruct callback, indicating that it has been destroyed. 254 * 255 * @param surfaceTexture The {@link SurfaceTexture}. 256 * @return {@code True} if the {@link TextureView} can release the {@link SurfaceTexture}. 257 */ 258 @Override 259 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 260 /** 261 * Destroying the surface texture; inform the presenter so it can null the surfaces. 262 */ 263 Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture=" 264 + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture 265 + " SavedSurface=" + mSavedSurface); 266 Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter); 267 268 // Notify presenter if it is not null. 269 onSurfaceDestroyed(); 270 271 if (mIsDoneWithSurface) { 272 onSurfaceReleased(); 273 if (mSavedSurface != null) { 274 mSavedSurface.release(); 275 mSavedSurface = null; 276 } 277 } 278 return mIsDoneWithSurface; 279 } 280 281 private void onSurfaceDestroyed() { 282 if (mPresenter != null) { 283 mPresenter.onSurfaceDestroyed(mSurfaceId); 284 } else { 285 Log.e(this, "onSurfaceTextureDestroyed: Presenter is null."); 286 } 287 } 288 289 /** 290 * Handles {@link SurfaceTexture} update callback. 291 * @param surface 292 */ 293 @Override 294 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 295 // Not Handled 296 } 297 298 @Override 299 public void onViewAttachedToWindow(View v) { 300 if (DEBUG) { 301 Log.i(TAG, "OnViewAttachedToWindow"); 302 } 303 if (mSavedSurfaceTexture != null) { 304 mTextureView.setSurfaceTexture(mSavedSurfaceTexture); 305 } 306 } 307 308 @Override 309 public void onViewDetachedFromWindow(View v) {} 310 311 /** 312 * Retrieves the current {@link TextureView}. 313 * 314 * @return The {@link TextureView}. 315 */ 316 public TextureView getTextureView() { 317 return mTextureView; 318 } 319 320 /** 321 * Called by the user presenter to indicate that the surface is no longer required due to a 322 * change in video state. Releases and clears out the saved surface and surface textures. 323 */ 324 public void setDoneWithSurface() { 325 Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface 326 + " SavedSurfaceTexture=" + mSavedSurfaceTexture); 327 mIsDoneWithSurface = true; 328 if (mTextureView != null && mTextureView.isAvailable()) { 329 return; 330 } 331 332 if (mSavedSurface != null) { 333 onSurfaceReleased(); 334 mSavedSurface.release(); 335 mSavedSurface = null; 336 } 337 if (mSavedSurfaceTexture != null) { 338 mSavedSurfaceTexture.release(); 339 mSavedSurfaceTexture = null; 340 } 341 } 342 343 private void onSurfaceReleased() { 344 if (mPresenter != null) { 345 mPresenter.onSurfaceReleased(mSurfaceId); 346 } else { 347 Log.d(this, "setDoneWithSurface: Presenter is null."); 348 } 349 } 350 351 /** 352 * Retrieves the saved surface instance. 353 * 354 * @return The surface. 355 */ 356 public Surface getSurface() { 357 return mSavedSurface; 358 } 359 360 /** 361 * Sets the dimensions of the surface. 362 * 363 * @param width The width of the surface, in pixels. 364 * @param height The height of the surface, in pixels. 365 */ 366 public void setSurfaceDimensions(int width, int height) { 367 Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height); 368 mWidth = width; 369 mHeight = height; 370 371 if (mSavedSurfaceTexture != null) { 372 Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null."); 373 createSurface(width, height); 374 } 375 } 376 377 /** 378 * Creates the {@link Surface}, adjusting the {@link SurfaceTexture} buffer size. 379 * @param width The width of the surface to create. 380 * @param height The height of the surface to create. 381 */ 382 private boolean createSurface(int width, int height) { 383 Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture 384 + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height); 385 if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET 386 && mSavedSurfaceTexture != null) { 387 mSavedSurfaceTexture.setDefaultBufferSize(width, height); 388 mSavedSurface = new Surface(mSavedSurfaceTexture); 389 return true; 390 } 391 return false; 392 } 393 394 /** 395 * Handles a user clicking the surface, which is the trigger to toggle the full screen 396 * Video UI. 397 * 398 * @param view The view receiving the click. 399 */ 400 @Override 401 public void onClick(View view) { 402 if (mPresenter != null) { 403 mPresenter.onSurfaceClick(mSurfaceId); 404 } else { 405 Log.e(this, "onClick: Presenter is null."); 406 } 407 } 408 409 /** 410 * Returns the dimensions of the surface. 411 * 412 * @return The dimensions of the surface. 413 */ 414 public Point getSurfaceDimensions() { 415 return new Point(mWidth, mHeight); 416 } 417 }; 418 419 @Override 420 public void onCreate(Bundle savedInstanceState) { 421 super.onCreate(savedInstanceState); 422 } 423 424 /** 425 * Handles creation of the activity and initialization of the presenter. 426 * 427 * @param savedInstanceState The saved instance state. 428 */ 429 @Override 430 public void onActivityCreated(Bundle savedInstanceState) { 431 super.onActivityCreated(savedInstanceState); 432 433 mIsLandscape = getResources().getBoolean(R.bool.is_layout_landscape); 434 435 Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape); 436 getPresenter().init(getActivity()); 437 } 438 439 @Override 440 public View onCreateView(LayoutInflater inflater, ViewGroup container, 441 Bundle savedInstanceState) { 442 super.onCreateView(inflater, container, savedInstanceState); 443 444 final View view = inflater.inflate(R.layout.video_call_fragment, container, false); 445 446 return view; 447 } 448 449 /** 450 * Centers the display view vertically for portrait orientations. The view is centered within 451 * the available space not occupied by the call card. This is a no-op for landscape mode. 452 * 453 * @param displayVideo The video view to center. 454 */ 455 private void centerDisplayView(View displayVideo) { 456 if (!mIsLandscape) { 457 float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard(); 458 float videoViewTranslation = displayVideo.getHeight() / 2 459 - spaceBesideCallCard / 2; 460 displayVideo.setTranslationY(videoViewTranslation); 461 } 462 } 463 464 /** 465 * After creation of the fragment view, retrieves the required views. 466 * 467 * @param view The fragment view. 468 * @param savedInstanceState The saved instance state. 469 */ 470 @Override 471 public void onViewCreated(View view, Bundle savedInstanceState) { 472 super.onViewCreated(view, savedInstanceState); 473 Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse); 474 475 mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub); 476 477 // If the surfaces are already in use, we have just changed orientation or otherwise 478 // re-created the fragment. In this case we need to inflate the video call views and 479 // restore the surfaces. 480 if (sVideoSurfacesInUse) { 481 inflateVideoCallViews(); 482 } 483 } 484 485 @Override 486 public void onStop() { 487 super.onStop(); 488 Log.d(this, "onStop:"); 489 } 490 491 @Override 492 public void onPause() { 493 super.onPause(); 494 Log.d(this, "onPause:"); 495 } 496 497 @Override 498 public void onDestroyView() { 499 super.onDestroyView(); 500 Log.d(this, "onDestroyView:"); 501 } 502 503 /** 504 * Creates the presenter for the {@link VideoCallFragment}. 505 * @return The presenter instance. 506 */ 507 @Override 508 public VideoCallPresenter createPresenter() { 509 Log.d(this, "createPresenter"); 510 VideoCallPresenter presenter = new VideoCallPresenter(); 511 onPresenterChanged(presenter); 512 return presenter; 513 } 514 515 /** 516 * @return The user interface for the presenter, which is this fragment. 517 */ 518 @Override 519 public VideoCallPresenter.VideoCallUi getUi() { 520 return this; 521 } 522 523 /** 524 * Inflate video surfaces. 525 * 526 * @param show {@code True} if the video surfaces should be shown. 527 */ 528 private void inflateVideoUi(boolean show) { 529 int visibility = show ? View.VISIBLE : View.GONE; 530 getView().setVisibility(visibility); 531 532 if (show) { 533 inflateVideoCallViews(); 534 } 535 536 if (mVideoViews != null) { 537 mVideoViews.setVisibility(visibility); 538 } 539 } 540 541 /** 542 * Hides and shows the incoming video view and changes the outgoing video view's state based on 543 * whether outgoing view is enabled or not. 544 */ 545 public void showVideoViews(boolean previewPaused, boolean showIncoming) { 546 inflateVideoUi(true); 547 548 View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo); 549 if (incomingVideoView != null) { 550 incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE); 551 } 552 if (mCameraOff != null) { 553 mCameraOff.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE); 554 } 555 if (mPreviewPhoto != null) { 556 mPreviewPhoto.setVisibility(!previewPaused ? View.VISIBLE : View.INVISIBLE); 557 } 558 } 559 560 /** 561 * Hide all video views. 562 */ 563 public void hideVideoUi() { 564 inflateVideoUi(false); 565 } 566 567 /** 568 * Cleans up the video telephony surfaces. Used when the presenter indicates a change to an 569 * audio-only state. Since the surfaces are static, it is important to ensure they are cleaned 570 * up promptly. 571 */ 572 @Override 573 public void cleanupSurfaces() { 574 Log.d(this, "cleanupSurfaces"); 575 if (sDisplaySurface != null) { 576 sDisplaySurface.setDoneWithSurface(); 577 sDisplaySurface = null; 578 } 579 if (sPreviewSurface != null) { 580 sPreviewSurface.setDoneWithSurface(); 581 sPreviewSurface = null; 582 } 583 sVideoSurfacesInUse = false; 584 } 585 586 @Override 587 public ImageView getPreviewPhotoView() { 588 return mPreviewPhoto; 589 } 590 591 private void onPresenterChanged(VideoCallPresenter presenter) { 592 Log.d(this, "onPresenterChanged: Presenter=" + presenter); 593 if (sDisplaySurface != null) { 594 sDisplaySurface.resetPresenter(presenter);; 595 } 596 if (sPreviewSurface != null) { 597 sPreviewSurface.resetPresenter(presenter); 598 } 599 } 600 601 /** 602 * @return {@code True} if the display video surface has been created. 603 */ 604 @Override 605 public boolean isDisplayVideoSurfaceCreated() { 606 boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null; 607 Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret); 608 return ret; 609 } 610 611 /** 612 * @return {@code True} if the preview video surface has been created. 613 */ 614 @Override 615 public boolean isPreviewVideoSurfaceCreated() { 616 boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null; 617 Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret); 618 return ret; 619 } 620 621 /** 622 * {@link android.view.Surface} on which incoming video for a video call is displayed. 623 * {@code Null} until the video views {@link android.view.ViewStub} is inflated. 624 */ 625 @Override 626 public Surface getDisplayVideoSurface() { 627 return sDisplaySurface == null ? null : sDisplaySurface.getSurface(); 628 } 629 630 /** 631 * {@link android.view.Surface} on which a preview of the outgoing video for a video call is 632 * displayed. {@code Null} until the video views {@link android.view.ViewStub} is inflated. 633 */ 634 @Override 635 public Surface getPreviewVideoSurface() { 636 return sPreviewSurface == null ? null : sPreviewSurface.getSurface(); 637 } 638 639 /** 640 * Changes the dimensions of the preview surface. Called when the dimensions change due to a 641 * device orientation change. 642 * 643 * @param width The new width. 644 * @param height The new height. 645 */ 646 @Override 647 public void setPreviewSize(int width, int height) { 648 Log.d(this, "setPreviewSize: width=" + width + " height=" + height); 649 if (sPreviewSurface != null) { 650 TextureView preview = sPreviewSurface.getTextureView(); 651 652 if (preview == null ) { 653 return; 654 } 655 656 // Set the dimensions of both the video surface and the FrameLayout containing it. 657 ViewGroup.LayoutParams params = preview.getLayoutParams(); 658 params.width = width; 659 params.height = height; 660 preview.setLayoutParams(params); 661 662 if (mPreviewVideoContainer != null) { 663 ViewGroup.LayoutParams containerParams = mPreviewVideoContainer.getLayoutParams(); 664 containerParams.width = width; 665 containerParams.height = height; 666 mPreviewVideoContainer.setLayoutParams(containerParams); 667 } 668 669 // The width and height are interchanged outside of this method based on the current 670 // orientation, so we can transform using "width", which will be either the width or 671 // the height. 672 Matrix transform = new Matrix(); 673 transform.setScale(-1, 1, width/2, 0); 674 preview.setTransform(transform); 675 } 676 } 677 678 @Override 679 public void setPreviewSurfaceSize(int width, int height) { 680 final boolean isPreviewSurfaceAvailable = sPreviewSurface != null; 681 Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height + 682 " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable); 683 if (isPreviewSurfaceAvailable) { 684 sPreviewSurface.setSurfaceDimensions(width, height); 685 } 686 } 687 688 /** 689 * returns UI's current orientation. 690 */ 691 @Override 692 public int getCurrentRotation() { 693 try { 694 return getActivity().getWindowManager().getDefaultDisplay().getRotation(); 695 } catch (Exception e) { 696 Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e); 697 } 698 return ORIENTATION_UNKNOWN; 699 } 700 701 /** 702 * Changes the dimensions of the display video surface. Called when the dimensions change due to 703 * a peer resolution update 704 * 705 * @param width The new width. 706 * @param height The new height. 707 */ 708 @Override 709 public void setDisplayVideoSize(int width, int height) { 710 Log.d(this, "setDisplayVideoSize: width=" + width + " height=" + height); 711 if (sDisplaySurface != null) { 712 TextureView displayVideo = sDisplaySurface.getTextureView(); 713 if (displayVideo == null) { 714 Log.e(this, "Display Video texture view is null. Bail out"); 715 return; 716 } 717 sDisplaySize = new Point(width, height); 718 setSurfaceSizeAndTranslation(displayVideo, sDisplaySize); 719 } else { 720 Log.e(this, "Display Video Surface is null. Bail out"); 721 } 722 } 723 724 /** 725 * Determines the size of the device screen. 726 * 727 * @return {@link Point} specifying the width and height of the screen. 728 */ 729 @Override 730 public Point getScreenSize() { 731 // Get current screen size. 732 Display display = getActivity().getWindowManager().getDefaultDisplay(); 733 Point size = new Point(); 734 display.getSize(size); 735 736 return size; 737 } 738 739 /** 740 * Determines the size of the preview surface. 741 * 742 * @return {@link Point} specifying the width and height of the preview surface. 743 */ 744 @Override 745 public Point getPreviewSize() { 746 if (sPreviewSurface == null) { 747 return null; 748 } 749 return sPreviewSurface.getSurfaceDimensions(); 750 } 751 752 /** 753 * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary, 754 * and creates {@link VideoCallSurface} instances to track the surfaces. 755 */ 756 private void inflateVideoCallViews() { 757 Log.d(this, "inflateVideoCallViews"); 758 if (mVideoViews == null ) { 759 mVideoViews = mVideoViewsStub.inflate(); 760 } 761 762 if (mVideoViews != null) { 763 mPreviewVideoContainer = mVideoViews.findViewById(R.id.previewVideoContainer); 764 mCameraOff = mVideoViews.findViewById(R.id.previewCameraOff); 765 mPreviewPhoto = (ImageView) mVideoViews.findViewById(R.id.previewProfilePhoto); 766 767 TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo); 768 769 Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse); 770 //If peer adjusted screen size is not available, set screen size to default display size 771 Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize; 772 setSurfaceSizeAndTranslation(displaySurface, screenSize); 773 774 if (!sVideoSurfacesInUse) { 775 // Where the video surfaces are not already in use (first time creating them), 776 // setup new VideoCallSurface instances to track them. 777 Log.d(this, " inflateVideoCallViews screenSize" + screenSize); 778 779 sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY, 780 (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x, 781 screenSize.y); 782 sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW, 783 (TextureView) mVideoViews.findViewById(R.id.previewVideo)); 784 sVideoSurfacesInUse = true; 785 } else { 786 // In this case, the video surfaces are already in use (we are recreating the 787 // Fragment after a destroy/create cycle resulting from a rotation. 788 sDisplaySurface.recreateView((TextureView) mVideoViews.findViewById( 789 R.id.incomingVideo)); 790 sPreviewSurface.recreateView((TextureView) mVideoViews.findViewById( 791 R.id.previewVideo)); 792 } 793 794 // Attempt to center the incoming video view, if it is in the layout. 795 final ViewTreeObserver observer = mVideoViews.getViewTreeObserver(); 796 observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 797 @Override 798 public void onGlobalLayout() { 799 // Check if the layout includes the incoming video surface -- this will only be the 800 // case for a video call. 801 View displayVideo = mVideoViews.findViewById(R.id.incomingVideo); 802 if (displayVideo != null) { 803 centerDisplayView(displayVideo); 804 } 805 mIsLayoutComplete = true; 806 807 // Remove the listener so we don't continually re-layout. 808 ViewTreeObserver observer = mVideoViews.getViewTreeObserver(); 809 if (observer.isAlive()) { 810 observer.removeOnGlobalLayoutListener(this); 811 } 812 } 813 }); 814 } 815 } 816 817 /** 818 * Resizes a surface so that it has the same size as the full screen and so that it is 819 * centered vertically below the call card. 820 * 821 * @param textureView The {@link TextureView} to resize and position. 822 * @param size The size of the screen. 823 */ 824 private void setSurfaceSizeAndTranslation(TextureView textureView, Point size) { 825 // Set the surface to have that size. 826 ViewGroup.LayoutParams params = textureView.getLayoutParams(); 827 params.width = size.x; 828 params.height = size.y; 829 textureView.setLayoutParams(params); 830 Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" + 831 mIsLayoutComplete + "IsLandscape=" + mIsLandscape); 832 833 // It is only possible to center the display view if layout of the views has completed. 834 // It is only after layout is complete that the dimensions of the Call Card has been 835 // established, which is a prerequisite to centering the view. 836 // Incoming video calls will center the view 837 if (mIsLayoutComplete) { 838 centerDisplayView(textureView); 839 } 840 } 841 } 842