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