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.Context; 20 import android.database.Cursor; 21 import android.graphics.Point; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.provider.ContactsContract; 27 import android.telecom.Connection; 28 import android.telecom.InCallService.VideoCall; 29 import android.telecom.VideoProfile; 30 import android.telecom.VideoProfile.CameraCapabilities; 31 import android.view.Surface; 32 import android.widget.ImageView; 33 34 import com.android.contacts.common.ContactPhotoManager; 35 import com.android.contacts.common.compat.CompatUtils; 36 import com.android.dialer.R; 37 import com.android.incallui.InCallPresenter.InCallDetailsListener; 38 import com.android.incallui.InCallPresenter.InCallOrientationListener; 39 import com.android.incallui.InCallPresenter.InCallStateListener; 40 import com.android.incallui.InCallPresenter.IncomingCallListener; 41 import com.android.incallui.InCallVideoCallCallbackNotifier.SurfaceChangeListener; 42 import com.android.incallui.InCallVideoCallCallbackNotifier.VideoEventListener; 43 44 import java.util.Objects; 45 46 /** 47 * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling 48 * surfaces based on other user interface events and incoming events from the 49 * {@class VideoCallListener}. 50 * <p> 51 * When a call's video state changes to bi-directional video, the 52 * {@link com.android.incallui.VideoCallPresenter} performs the following negotiation with the 53 * telephony layer: 54 * <ul> 55 * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.</li> 56 * <li>{@code VideoCallPresenter} creates the preview surface.</li> 57 * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.</li> 58 * <li>Telephony layer sends {@link CameraCapabilities}, including the 59 * dimensions of the video for the current camera.</li> 60 * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect 61 * ratio of the camera.</li> 62 * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.</li> 63 * </ul> 64 * <p> 65 * When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both 66 * surfaces. 67 */ 68 public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi> implements 69 IncomingCallListener, InCallOrientationListener, InCallStateListener, 70 InCallDetailsListener, SurfaceChangeListener, VideoEventListener, 71 InCallPresenter.InCallEventListener { 72 public static final String TAG = "VideoCallPresenter"; 73 74 public static final boolean DEBUG = false; 75 76 /** 77 * Runnable which is posted to schedule automatically entering fullscreen mode. Will not auto 78 * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit 79 * the dialpad). 80 */ 81 private Runnable mAutoFullscreenRunnable = new Runnable() { 82 @Override 83 public void run() { 84 if (mAutoFullScreenPending && !InCallPresenter.getInstance().isDialpadVisible() 85 && mIsVideoMode) { 86 87 Log.v(this, "Automatically entering fullscreen mode."); 88 InCallPresenter.getInstance().setFullScreen(true); 89 mAutoFullScreenPending = false; 90 } else { 91 Log.v(this, "Skipping scheduled fullscreen mode."); 92 } 93 } 94 }; 95 96 /** 97 * Defines the state of the preview surface negotiation with the telephony layer. 98 */ 99 private class PreviewSurfaceState { 100 /** 101 * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet 102 * started. 103 */ 104 private static final int NONE = 0; 105 106 /** 107 * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet 108 * been received. 109 */ 110 private static final int CAMERA_SET = 1; 111 112 /** 113 * The camera capabilties have been received from telephony, but the surface has not yet 114 * been set on the {@link VideoCall}. 115 */ 116 private static final int CAPABILITIES_RECEIVED = 2; 117 118 /** 119 * The surface has been set on the {@link VideoCall}. 120 */ 121 private static final int SURFACE_SET = 3; 122 } 123 124 /** 125 * The minimum width or height of the preview surface. Used when re-sizing the preview surface 126 * to match the aspect ratio of the currently selected camera. 127 */ 128 private float mMinimumVideoDimension; 129 130 /** 131 * The current context. 132 */ 133 private Context mContext; 134 135 /** 136 * The call the video surfaces are currently related to 137 */ 138 private Call mPrimaryCall; 139 140 /** 141 * The {@link VideoCall} used to inform the video telephony layer of changes to the video 142 * surfaces. 143 */ 144 private VideoCall mVideoCall; 145 146 /** 147 * Determines if the current UI state represents a video call. 148 */ 149 private int mCurrentVideoState; 150 151 /** 152 * Call's current state 153 */ 154 private int mCurrentCallState = Call.State.INVALID; 155 156 /** 157 * Determines the device orientation (portrait/lanscape). 158 */ 159 private int mDeviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_0; 160 161 /** 162 * Tracks the state of the preview surface negotiation with the telephony layer. 163 */ 164 private int mPreviewSurfaceState = PreviewSurfaceState.NONE; 165 166 private static boolean mIsVideoMode = false; 167 168 /** 169 * Contact photo manager to retrieve cached contact photo information. 170 */ 171 private ContactPhotoManager mContactPhotoManager = null; 172 173 /** 174 * The URI for the user's profile photo, or {@code null} if not specified. 175 */ 176 private ContactInfoCache.ContactCacheEntry mProfileInfo = null; 177 178 /** 179 * UI thread handler used for delayed task execution. 180 */ 181 private Handler mHandler; 182 183 /** 184 * Determines whether video calls should automatically enter full screen mode after 185 * {@link #mAutoFullscreenTimeoutMillis} milliseconds. 186 */ 187 private boolean mIsAutoFullscreenEnabled = false; 188 189 /** 190 * Determines the number of milliseconds after which a video call will automatically enter 191 * fullscreen mode. Requires {@link #mIsAutoFullscreenEnabled} to be {@code true}. 192 */ 193 private int mAutoFullscreenTimeoutMillis = 0; 194 195 /** 196 * Determines if the countdown is currently running to automatically enter full screen video 197 * mode. 198 */ 199 private boolean mAutoFullScreenPending = false; 200 201 /** 202 * Initializes the presenter. 203 * 204 * @param context The current context. 205 */ 206 public void init(Context context) { 207 mContext = context; 208 mMinimumVideoDimension = mContext.getResources().getDimension( 209 R.dimen.video_preview_small_dimension); 210 mHandler = new Handler(Looper.getMainLooper()); 211 mIsAutoFullscreenEnabled = mContext.getResources() 212 .getBoolean(R.bool.video_call_auto_fullscreen); 213 mAutoFullscreenTimeoutMillis = mContext.getResources().getInteger( 214 R.integer.video_call_auto_fullscreen_timeout); 215 } 216 217 /** 218 * Called when the user interface is ready to be used. 219 * 220 * @param ui The Ui implementation that is now ready to be used. 221 */ 222 @Override 223 public void onUiReady(VideoCallUi ui) { 224 super.onUiReady(ui); 225 Log.d(this, "onUiReady:"); 226 227 // Do not register any listeners if video calling is not compatible to safeguard against 228 // any accidental calls of video calling code. 229 if (!CompatUtils.isVideoCompatible()) { 230 return; 231 } 232 233 // Register for call state changes last 234 InCallPresenter.getInstance().addListener(this); 235 InCallPresenter.getInstance().addDetailsListener(this); 236 InCallPresenter.getInstance().addIncomingCallListener(this); 237 InCallPresenter.getInstance().addOrientationListener(this); 238 // To get updates of video call details changes 239 InCallPresenter.getInstance().addDetailsListener(this); 240 InCallPresenter.getInstance().addInCallEventListener(this); 241 242 // Register for surface and video events from {@link InCallVideoCallListener}s. 243 InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this); 244 InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this); 245 mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY; 246 mCurrentCallState = Call.State.INVALID; 247 248 final InCallPresenter.InCallState inCallState = 249 InCallPresenter.getInstance().getInCallState(); 250 onStateChange(inCallState, inCallState, CallList.getInstance()); 251 } 252 253 /** 254 * Called when the user interface is no longer ready to be used. 255 * 256 * @param ui The Ui implementation that is no longer ready to be used. 257 */ 258 @Override 259 public void onUiUnready(VideoCallUi ui) { 260 super.onUiUnready(ui); 261 Log.d(this, "onUiUnready:"); 262 263 if (!CompatUtils.isVideoCompatible()) { 264 return; 265 } 266 267 cancelAutoFullScreen(); 268 269 InCallPresenter.getInstance().removeListener(this); 270 InCallPresenter.getInstance().removeDetailsListener(this); 271 InCallPresenter.getInstance().removeIncomingCallListener(this); 272 InCallPresenter.getInstance().removeOrientationListener(this); 273 InCallPresenter.getInstance().removeInCallEventListener(this); 274 275 InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this); 276 InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this); 277 } 278 279 /** 280 * Handles the creation of a surface in the {@link VideoCallFragment}. 281 * 282 * @param surface The surface which was created. 283 */ 284 public void onSurfaceCreated(int surface) { 285 Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall); 286 Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState); 287 Log.d(this, "onSurfaceCreated presenter=" + this); 288 289 final VideoCallUi ui = getUi(); 290 if (ui == null || mVideoCall == null) { 291 Log.w(this, "onSurfaceCreated: Error bad state VideoCallUi=" + ui + " mVideoCall=" 292 + mVideoCall); 293 return; 294 } 295 296 // If the preview surface has just been created and we have already received camera 297 // capabilities, but not yet set the surface, we will set the surface now. 298 if (surface == VideoCallFragment.SURFACE_PREVIEW ) { 299 if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { 300 mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; 301 mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); 302 } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){ 303 enableCamera(mVideoCall, true); 304 } 305 } else if (surface == VideoCallFragment.SURFACE_DISPLAY) { 306 mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); 307 } 308 } 309 310 /** 311 * Handles structural changes (format or size) to a surface. 312 * 313 * @param surface The surface which changed. 314 * @param format The new PixelFormat of the surface. 315 * @param width The new width of the surface. 316 * @param height The new height of the surface. 317 */ 318 public void onSurfaceChanged(int surface, int format, int width, int height) { 319 //Do stuff 320 } 321 322 /** 323 * Handles the destruction of a surface in the {@link VideoCallFragment}. 324 * Note: The surface is being released, that is, it is no longer valid. 325 * 326 * @param surface The surface which was destroyed. 327 */ 328 public void onSurfaceReleased(int surface) { 329 Log.d(this, "onSurfaceReleased: mSurfaceId=" + surface); 330 if ( mVideoCall == null) { 331 Log.w(this, "onSurfaceReleased: VideoCall is null. mSurfaceId=" + 332 surface); 333 return; 334 } 335 336 if (surface == VideoCallFragment.SURFACE_DISPLAY) { 337 mVideoCall.setDisplaySurface(null); 338 } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { 339 mVideoCall.setPreviewSurface(null); 340 enableCamera(mVideoCall, false); 341 } 342 } 343 344 /** 345 * Called by {@link VideoCallFragment} when the surface is detached from UI (TextureView). 346 * Note: The surface will be cached by {@link VideoCallFragment}, so we don't immediately 347 * null out incoming video surface. 348 * @see VideoCallPresenter#onSurfaceReleased(int) 349 * 350 * @param surface The surface which was detached. 351 */ 352 public void onSurfaceDestroyed(int surface) { 353 Log.d(this, "onSurfaceDestroyed: mSurfaceId=" + surface); 354 if (mVideoCall == null) { 355 return; 356 } 357 358 final boolean isChangingConfigurations = 359 InCallPresenter.getInstance().isChangingConfigurations(); 360 Log.d(this, "onSurfaceDestroyed: isChangingConfigurations=" + isChangingConfigurations); 361 362 if (surface == VideoCallFragment.SURFACE_PREVIEW) { 363 if (!isChangingConfigurations) { 364 enableCamera(mVideoCall, false); 365 } else { 366 Log.w(this, "onSurfaceDestroyed: Activity is being destroyed due " 367 + "to configuration changes. Not closing the camera."); 368 } 369 } 370 } 371 372 /** 373 * Handles clicks on the video surfaces by toggling full screen state. 374 * Informs the {@link InCallPresenter} of the change so that it can inform the 375 * {@link CallCardPresenter} of the change. 376 * 377 * @param surfaceId The video surface receiving the click. 378 */ 379 public void onSurfaceClick(int surfaceId) { 380 boolean isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode(); 381 Log.v(this, "toggleFullScreen = " + isFullscreen); 382 } 383 384 /** 385 * Handles incoming calls. 386 * 387 * @param oldState The old in call state. 388 * @param newState The new in call state. 389 * @param call The call. 390 */ 391 @Override 392 public void onIncomingCall(InCallPresenter.InCallState oldState, 393 InCallPresenter.InCallState newState, Call call) { 394 // same logic should happen as with onStateChange() 395 onStateChange(oldState, newState, CallList.getInstance()); 396 } 397 398 /** 399 * Handles state changes (including incoming calls) 400 * 401 * @param newState The in call state. 402 * @param callList The call list. 403 */ 404 @Override 405 public void onStateChange(InCallPresenter.InCallState oldState, 406 InCallPresenter.InCallState newState, CallList callList) { 407 Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState + 408 " isVideoMode=" + isVideoMode()); 409 410 if (newState == InCallPresenter.InCallState.NO_CALLS) { 411 if (isVideoMode()) { 412 exitVideoMode(); 413 } 414 415 cleanupSurfaces(); 416 } 417 418 // Determine the primary active call). 419 Call primary = null; 420 421 // Determine the call which is the focus of the user's attention. In the case of an 422 // incoming call waiting call, the primary call is still the active video call, however 423 // the determination of whether we should be in fullscreen mode is based on the type of the 424 // incoming call, not the active video call. 425 Call currentCall = null; 426 427 if (newState == InCallPresenter.InCallState.INCOMING) { 428 // We don't want to replace active video call (primary call) 429 // with a waiting call, since user may choose to ignore/decline the waiting call and 430 // this should have no impact on current active video call, that is, we should not 431 // change the camera or UI unless the waiting VT call becomes active. 432 primary = callList.getActiveCall(); 433 currentCall = callList.getIncomingCall(); 434 if (!VideoUtils.isActiveVideoCall(primary)) { 435 primary = callList.getIncomingCall(); 436 } 437 } else if (newState == InCallPresenter.InCallState.OUTGOING) { 438 currentCall = primary = callList.getOutgoingCall(); 439 } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) { 440 currentCall = primary = callList.getPendingOutgoingCall(); 441 } else if (newState == InCallPresenter.InCallState.INCALL) { 442 currentCall = primary = callList.getActiveCall(); 443 } 444 445 final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary); 446 Log.d(this, "onStateChange primaryChanged=" + primaryChanged); 447 Log.d(this, "onStateChange primary= " + primary); 448 Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall); 449 if (primaryChanged) { 450 onPrimaryCallChanged(primary); 451 } else if (mPrimaryCall != null) { 452 updateVideoCall(primary); 453 } 454 updateCallCache(primary); 455 456 // If the call context changed, potentially exit fullscreen or schedule auto enter of 457 // fullscreen mode. 458 // If the current call context is no longer a video call, exit fullscreen mode. 459 maybeExitFullscreen(currentCall); 460 // Schedule auto-enter of fullscreen mode if the current call context is a video call 461 maybeAutoEnterFullscreen(currentCall); 462 } 463 464 /** 465 * Handles a change to the fullscreen mode of the app. 466 * 467 * @param isFullscreenMode {@code true} if the app is now fullscreen, {@code false} otherwise. 468 */ 469 @Override 470 public void onFullscreenModeChanged(boolean isFullscreenMode) { 471 cancelAutoFullScreen(); 472 } 473 474 /** 475 * Handles changes to the visibility of the secondary caller info bar. 476 * 477 * @param isVisible {@code true} if the secondary caller info is showing, {@code false} 478 * otherwise. 479 * @param height the height of the secondary caller info bar. 480 */ 481 @Override 482 public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) { 483 Log.d(this, 484 "onSecondaryCallerInfoVisibilityChanged : isVisible = " + isVisible + " height = " 485 + height); 486 getUi().adjustPreviewLocation(isVisible /* shiftUp */, height); 487 } 488 489 private void checkForVideoStateChange(Call call) { 490 final boolean isVideoCall = VideoUtils.isVideoCall(call); 491 final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState(); 492 493 Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall 494 + " hasVideoStateChanged=" + hasVideoStateChanged + " isVideoMode=" 495 + isVideoMode() + " previousVideoState: " + 496 VideoProfile.videoStateToString(mCurrentVideoState) + " newVideoState: " 497 + VideoProfile.videoStateToString(call.getVideoState())); 498 499 if (!hasVideoStateChanged) { 500 return; 501 } 502 503 updateCameraSelection(call); 504 505 if (isVideoCall) { 506 adjustVideoMode(call); 507 } else if (isVideoMode()) { 508 exitVideoMode(); 509 } 510 } 511 512 private void checkForCallStateChange(Call call) { 513 final boolean isVideoCall = VideoUtils.isVideoCall(call); 514 final boolean hasCallStateChanged = mCurrentCallState != call.getState(); 515 516 Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall 517 + " hasCallStateChanged=" + 518 hasCallStateChanged + " isVideoMode=" + isVideoMode()); 519 520 if (!hasCallStateChanged) { 521 return; 522 } 523 524 if (isVideoCall) { 525 final InCallCameraManager cameraManager = InCallPresenter.getInstance(). 526 getInCallCameraManager(); 527 528 String prevCameraId = cameraManager.getActiveCameraId(); 529 updateCameraSelection(call); 530 String newCameraId = cameraManager.getActiveCameraId(); 531 532 if (!Objects.equals(prevCameraId, newCameraId) && VideoUtils.isActiveVideoCall(call)) { 533 enableCamera(call.getVideoCall(), true); 534 } 535 } 536 537 // Make sure we hide or show the video UI if needed. 538 showVideoUi(call.getVideoState(), call.getState()); 539 } 540 541 private void cleanupSurfaces() { 542 final VideoCallUi ui = getUi(); 543 if (ui == null) { 544 Log.w(this, "cleanupSurfaces"); 545 return; 546 } 547 ui.cleanupSurfaces(); 548 } 549 550 private void onPrimaryCallChanged(Call newPrimaryCall) { 551 final boolean isVideoCall = VideoUtils.isVideoCall(newPrimaryCall); 552 final boolean isVideoMode = isVideoMode(); 553 554 Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode=" 555 + isVideoMode); 556 557 if (!isVideoCall && isVideoMode) { 558 // Terminate video mode if new primary call is not a video call 559 // and we are currently in video mode. 560 Log.d(this, "onPrimaryCallChanged: Exiting video mode..."); 561 exitVideoMode(); 562 } else if (isVideoCall) { 563 Log.d(this, "onPrimaryCallChanged: Entering video mode..."); 564 565 updateCameraSelection(newPrimaryCall); 566 adjustVideoMode(newPrimaryCall); 567 } 568 checkForOrientationAllowedChange(newPrimaryCall); 569 } 570 571 private boolean isVideoMode() { 572 return mIsVideoMode; 573 } 574 575 private void updateCallCache(Call call) { 576 if (call == null) { 577 mCurrentVideoState = VideoProfile.STATE_AUDIO_ONLY; 578 mCurrentCallState = Call.State.INVALID; 579 mVideoCall = null; 580 mPrimaryCall = null; 581 } else { 582 mCurrentVideoState = call.getVideoState(); 583 mVideoCall = call.getVideoCall(); 584 mCurrentCallState = call.getState(); 585 mPrimaryCall = call; 586 } 587 } 588 589 /** 590 * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in 591 * changes to the video state. 592 * 593 * @param call The call for which the details changed. 594 * @param details The new call details. 595 */ 596 @Override 597 public void onDetailsChanged(Call call, android.telecom.Call.Details details) { 598 Log.d(this, " onDetailsChanged call=" + call + " details=" + details + " mPrimaryCall=" 599 + mPrimaryCall); 600 if (call == null) { 601 return; 602 } 603 // If the details change is not for the currently active call no update is required. 604 if (!call.equals(mPrimaryCall)) { 605 Log.d(this, " onDetailsChanged: Details not for current active call so returning. "); 606 return; 607 } 608 609 updateVideoCall(call); 610 611 updateCallCache(call); 612 } 613 614 private void updateVideoCall(Call call) { 615 checkForVideoCallChange(call); 616 checkForVideoStateChange(call); 617 checkForCallStateChange(call); 618 checkForOrientationAllowedChange(call); 619 } 620 621 private void checkForOrientationAllowedChange(Call call) { 622 InCallPresenter.getInstance().setInCallAllowsOrientationChange( 623 VideoUtils.isVideoCall(call)); 624 } 625 626 /** 627 * Checks for a change to the video call and changes it if required. 628 */ 629 private void checkForVideoCallChange(Call call) { 630 final VideoCall videoCall = call.getTelecomCall().getVideoCall(); 631 Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall=" 632 + mVideoCall); 633 if (!Objects.equals(videoCall, mVideoCall)) { 634 changeVideoCall(call); 635 } 636 } 637 638 /** 639 * Handles a change to the video call. Sets the surfaces on the previous call to null and sets 640 * the surfaces on the new video call accordingly. 641 * 642 * @param call The new video call. 643 */ 644 private void changeVideoCall(Call call) { 645 final VideoCall videoCall = call.getTelecomCall().getVideoCall(); 646 Log.d(this, "changeVideoCall to videoCall=" + videoCall + " mVideoCall=" + mVideoCall); 647 // Null out the surfaces on the previous video call. 648 if (mVideoCall != null) { 649 // Log.d(this, "Null out the surfaces on the previous video call."); 650 // mVideoCall.setDisplaySurface(null); 651 // mVideoCall.setPreviewSurface(null); 652 } 653 654 final boolean hasChanged = mVideoCall == null && videoCall != null; 655 656 mVideoCall = videoCall; 657 if (mVideoCall == null || call == null) { 658 Log.d(this, "Video call or primary call is null. Return"); 659 return; 660 } 661 662 if (VideoUtils.isVideoCall(call) && hasChanged) { 663 adjustVideoMode(call); 664 } 665 } 666 667 private static boolean isCameraRequired(int videoState) { 668 return VideoProfile.isBidirectional(videoState) 669 || VideoProfile.isTransmissionEnabled(videoState); 670 } 671 672 private boolean isCameraRequired() { 673 return mPrimaryCall != null && isCameraRequired(mPrimaryCall.getVideoState()); 674 } 675 676 /** 677 * Adjusts the current video mode by setting up the preview and display surfaces as necessary. 678 * Expected to be called whenever the video state associated with a call changes (e.g. a user 679 * turns their camera on or off) to ensure the correct surfaces are shown/hidden. 680 * TODO(vt): Need to adjust size and orientation of preview surface here. 681 */ 682 private void adjustVideoMode(Call call) { 683 VideoCall videoCall = call.getVideoCall(); 684 int newVideoState = call.getVideoState(); 685 686 Log.d(this, "adjustVideoMode videoCall= " + videoCall + " videoState: " + newVideoState); 687 VideoCallUi ui = getUi(); 688 if (ui == null) { 689 Log.e(this, "Error VideoCallUi is null so returning"); 690 return; 691 } 692 693 showVideoUi(newVideoState, call.getState()); 694 695 // Communicate the current camera to telephony and make a request for the camera 696 // capabilities. 697 if (videoCall != null) { 698 if (ui.isDisplayVideoSurfaceCreated()) { 699 Log.d(this, "Calling setDisplaySurface with " + ui.getDisplayVideoSurface()); 700 videoCall.setDisplaySurface(ui.getDisplayVideoSurface()); 701 } 702 703 videoCall.setDeviceOrientation(mDeviceOrientation); 704 enableCamera(videoCall, isCameraRequired(newVideoState)); 705 } 706 int previousVideoState = mCurrentVideoState; 707 mCurrentVideoState = newVideoState; 708 mIsVideoMode = true; 709 710 // adjustVideoMode may be called if we are already in a 1-way video state. In this case 711 // we do not want to trigger auto-fullscreen mode. 712 if (!VideoUtils.isVideoCall(previousVideoState) && VideoUtils.isVideoCall(newVideoState)) { 713 maybeAutoEnterFullscreen(call); 714 } 715 } 716 717 private void enableCamera(VideoCall videoCall, boolean isCameraRequired) { 718 Log.d(this, "enableCamera: VideoCall=" + videoCall + " enabling=" + isCameraRequired); 719 if (videoCall == null) { 720 Log.w(this, "enableCamera: VideoCall is null."); 721 return; 722 } 723 724 if (isCameraRequired) { 725 InCallCameraManager cameraManager = InCallPresenter.getInstance(). 726 getInCallCameraManager(); 727 videoCall.setCamera(cameraManager.getActiveCameraId()); 728 mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; 729 730 videoCall.requestCameraCapabilities(); 731 } else { 732 mPreviewSurfaceState = PreviewSurfaceState.NONE; 733 videoCall.setCamera(null); 734 } 735 } 736 737 /** 738 * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). 739 */ 740 private void exitVideoMode() { 741 Log.d(this, "exitVideoMode"); 742 743 showVideoUi(VideoProfile.STATE_AUDIO_ONLY, Call.State.ACTIVE); 744 enableCamera(mVideoCall, false); 745 InCallPresenter.getInstance().setFullScreen(false); 746 747 mIsVideoMode = false; 748 } 749 750 /** 751 * Based on the current video state and call state, show or hide the incoming and 752 * outgoing video surfaces. The outgoing video surface is shown any time video is transmitting. 753 * The incoming video surface is shown whenever the video is un-paused and active. 754 * 755 * @param videoState The video state. 756 * @param callState The call state. 757 */ 758 private void showVideoUi(int videoState, int callState) { 759 VideoCallUi ui = getUi(); 760 if (ui == null) { 761 Log.e(this, "showVideoUi, VideoCallUi is null returning"); 762 return; 763 } 764 boolean showIncomingVideo = showIncomingVideo(videoState, callState); 765 boolean showOutgoingVideo = showOutgoingVideo(videoState); 766 Log.v(this, "showVideoUi : showIncoming = " + showIncomingVideo + " showOutgoing = " 767 + showOutgoingVideo); 768 if (showIncomingVideo || showOutgoingVideo) { 769 ui.showVideoViews(showOutgoingVideo, showIncomingVideo); 770 771 if (VideoProfile.isReceptionEnabled(videoState)) { 772 loadProfilePhotoAsync(); 773 } 774 } else { 775 ui.hideVideoUi(); 776 } 777 778 InCallPresenter.getInstance().enableScreenTimeout( 779 VideoProfile.isAudioOnly(videoState)); 780 } 781 782 /** 783 * Determines if the incoming video surface should be shown based on the current videoState and 784 * callState. The video surface is shown when incoming video is not paused, the call is active, 785 * and video reception is enabled. 786 * 787 * @param videoState The current video state. 788 * @param callState The current call state. 789 * @return {@code true} if the incoming video surface should be shown, {@code false} otherwise. 790 */ 791 public static boolean showIncomingVideo(int videoState, int callState) { 792 if (!CompatUtils.isVideoCompatible()) { 793 return false; 794 } 795 796 boolean isPaused = VideoProfile.isPaused(videoState); 797 boolean isCallActive = callState == Call.State.ACTIVE; 798 799 return !isPaused && isCallActive && VideoProfile.isReceptionEnabled(videoState); 800 } 801 802 /** 803 * Determines if the outgoing video surface should be shown based on the current videoState. 804 * The video surface is shown if video transmission is enabled. 805 * 806 * @param videoState The current video state. 807 * @return {@code true} if the the outgoing video surface should be shown, {@code false} 808 * otherwise. 809 */ 810 public static boolean showOutgoingVideo(int videoState) { 811 if (!CompatUtils.isVideoCompatible()) { 812 return false; 813 } 814 815 return VideoProfile.isTransmissionEnabled(videoState); 816 } 817 818 /** 819 * Handles peer video pause state changes. 820 * 821 * @param call The call which paused or un-pausedvideo transmission. 822 * @param paused {@code True} when the video transmission is paused, {@code false} when video 823 * transmission resumes. 824 */ 825 @Override 826 public void onPeerPauseStateChanged(Call call, boolean paused) { 827 if (!call.equals(mPrimaryCall)) { 828 return; 829 } 830 831 // TODO(vt): Show/hide the peer contact photo. 832 } 833 834 /** 835 * Handles peer video dimension changes. 836 * 837 * @param call The call which experienced a peer video dimension change. 838 * @param width The new peer video width . 839 * @param height The new peer video height. 840 */ 841 @Override 842 public void onUpdatePeerDimensions(Call call, int width, int height) { 843 Log.d(this, "onUpdatePeerDimensions: width= " + width + " height= " + height); 844 VideoCallUi ui = getUi(); 845 if (ui == null) { 846 Log.e(this, "VideoCallUi is null. Bail out"); 847 return; 848 } 849 if (!call.equals(mPrimaryCall)) { 850 Log.e(this, "Current call is not equal to primary call. Bail out"); 851 return; 852 } 853 854 // Change size of display surface to match the peer aspect ratio 855 if (width > 0 && height > 0) { 856 setDisplayVideoSize(width, height); 857 } 858 } 859 860 /** 861 * Handles any video quality changes in the call. 862 * 863 * @param call The call which experienced a video quality change. 864 * @param videoQuality The new video call quality. 865 */ 866 @Override 867 public void onVideoQualityChanged(Call call, int videoQuality) { 868 // No-op 869 } 870 871 /** 872 * Handles a change to the dimensions of the local camera. Receiving the camera capabilities 873 * triggers the creation of the video 874 * 875 * @param call The call which experienced the camera dimension change. 876 * @param width The new camera video width. 877 * @param height The new camera video height. 878 */ 879 @Override 880 public void onCameraDimensionsChange(Call call, int width, int height) { 881 Log.d(this, "onCameraDimensionsChange call=" + call + " width=" + width + " height=" 882 + height); 883 VideoCallUi ui = getUi(); 884 if (ui == null) { 885 Log.e(this, "onCameraDimensionsChange ui is null"); 886 return; 887 } 888 889 if (!call.equals(mPrimaryCall)) { 890 Log.e(this, "Call is not primary call"); 891 return; 892 } 893 894 mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED; 895 changePreviewDimensions(width, height); 896 897 // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}. 898 // If it not yet ready, it will be set when when creation completes. 899 if (ui.isPreviewVideoSurfaceCreated()) { 900 mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; 901 mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); 902 } 903 } 904 905 /** 906 * Changes the dimensions of the preview surface. 907 * 908 * @param width The new width. 909 * @param height The new height. 910 */ 911 private void changePreviewDimensions(int width, int height) { 912 VideoCallUi ui = getUi(); 913 if (ui == null) { 914 return; 915 } 916 917 // Resize the surface used to display the preview video 918 ui.setPreviewSurfaceSize(width, height); 919 920 // Configure the preview surface to the correct aspect ratio. 921 float aspectRatio = 1.0f; 922 if (width > 0 && height > 0) { 923 aspectRatio = (float) width / (float) height; 924 } 925 926 // Resize the textureview housing the preview video and rotate it appropriately based on 927 // the device orientation 928 setPreviewSize(mDeviceOrientation, aspectRatio); 929 } 930 931 /** 932 * Called when call session event is raised. 933 * 934 * @param event The call session event. 935 */ 936 @Override 937 public void onCallSessionEvent(int event) { 938 StringBuilder sb = new StringBuilder(); 939 sb.append("onCallSessionEvent = "); 940 941 switch (event) { 942 case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE: 943 sb.append("rx_pause"); 944 break; 945 case Connection.VideoProvider.SESSION_EVENT_RX_RESUME: 946 sb.append("rx_resume"); 947 break; 948 case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE: 949 sb.append("camera_failure"); 950 break; 951 case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY: 952 sb.append("camera_ready"); 953 break; 954 default: 955 sb.append("unknown event = "); 956 sb.append(event); 957 break; 958 } 959 Log.d(this, sb.toString()); 960 } 961 962 /** 963 * Handles a change to the call data usage 964 * 965 * @param dataUsage call data usage value 966 */ 967 @Override 968 public void onCallDataUsageChange(long dataUsage) { 969 Log.d(this, "onCallDataUsageChange dataUsage=" + dataUsage); 970 } 971 972 /** 973 * Handles changes to the device orientation. 974 * @param orientation The screen orientation of the device (one of: 975 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0}, 976 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90}, 977 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180}, 978 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}). 979 */ 980 @Override 981 public void onDeviceOrientationChanged(int orientation) { 982 mDeviceOrientation = orientation; 983 984 VideoCallUi ui = getUi(); 985 if (ui == null) { 986 Log.e(this, "onDeviceOrientationChanged: VideoCallUi is null"); 987 return; 988 } 989 990 Point previewDimensions = ui.getPreviewSize(); 991 if (previewDimensions == null) { 992 return; 993 } 994 Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation + " size: " 995 + previewDimensions); 996 changePreviewDimensions(previewDimensions.x, previewDimensions.y); 997 998 ui.setPreviewRotation(mDeviceOrientation); 999 } 1000 1001 /** 1002 * Sets the preview surface size based on the current device orientation. 1003 * See: {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0}, 1004 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90}, 1005 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180}, 1006 * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}). 1007 * 1008 * @param orientation The device orientation 1009 * @param aspectRatio The aspect ratio of the camera (width / height). 1010 */ 1011 private void setPreviewSize(int orientation, float aspectRatio) { 1012 VideoCallUi ui = getUi(); 1013 if (ui == null) { 1014 return; 1015 } 1016 1017 int height; 1018 int width; 1019 1020 if (orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_90 || 1021 orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_270) { 1022 width = (int) (mMinimumVideoDimension * aspectRatio); 1023 height = (int) mMinimumVideoDimension; 1024 } else { 1025 // Portrait or reverse portrait orientation. 1026 width = (int) mMinimumVideoDimension; 1027 height = (int) (mMinimumVideoDimension * aspectRatio); 1028 } 1029 ui.setPreviewSize(width, height); 1030 } 1031 1032 /** 1033 * Sets the display video surface size based on peer width and height 1034 * 1035 * @param width peer width 1036 * @param height peer height 1037 */ 1038 private void setDisplayVideoSize(int width, int height) { 1039 Log.v(this, "setDisplayVideoSize: Received peer width=" + width + " height=" + height); 1040 VideoCallUi ui = getUi(); 1041 if (ui == null) { 1042 return; 1043 } 1044 1045 // Get current display size 1046 Point size = ui.getScreenSize(); 1047 Log.v(this, "setDisplayVideoSize: windowmgr width=" + size.x 1048 + " windowmgr height=" + size.y); 1049 if (size.y * width > size.x * height) { 1050 // current display height is too much. Correct it 1051 size.y = (int) (size.x * height / width); 1052 } else if (size.y * width < size.x * height) { 1053 // current display width is too much. Correct it 1054 size.x = (int) (size.y * width / height); 1055 } 1056 ui.setDisplayVideoSize(size.x, size.y); 1057 } 1058 1059 /** 1060 * Exits fullscreen mode if the current call context has changed to a non-video call. 1061 * 1062 * @param call The call. 1063 */ 1064 protected void maybeExitFullscreen(Call call) { 1065 if (call == null) { 1066 return; 1067 } 1068 1069 if (!VideoUtils.isVideoCall(call) || call.getState() == Call.State.INCOMING) { 1070 InCallPresenter.getInstance().setFullScreen(false); 1071 } 1072 } 1073 1074 /** 1075 * Schedules auto-entering of fullscreen mode. 1076 * Will not enter full screen mode if any of the following conditions are met: 1077 * 1. No call 1078 * 2. Call is not active 1079 * 3. Call is not video call 1080 * 4. Already in fullscreen mode 1081 * 5. The current video state is not bi-directional (if the remote party stops transmitting, 1082 * the user's contact photo would dominate in fullscreen mode). 1083 * 1084 * @param call The current call. 1085 */ 1086 protected void maybeAutoEnterFullscreen(Call call) { 1087 if (!mIsAutoFullscreenEnabled) { 1088 return; 1089 } 1090 1091 if (call == null || ( 1092 call != null && (call.getState() != Call.State.ACTIVE || 1093 !VideoUtils.isVideoCall(call)) || 1094 InCallPresenter.getInstance().isFullscreen()) || 1095 !VideoUtils.isBidirectionalVideoCall(call)) { 1096 // Ensure any previously scheduled attempt to enter fullscreen is cancelled. 1097 cancelAutoFullScreen(); 1098 return; 1099 } 1100 1101 if (mAutoFullScreenPending) { 1102 Log.v(this, "maybeAutoEnterFullscreen : already pending."); 1103 return; 1104 } 1105 Log.v(this, "maybeAutoEnterFullscreen : scheduled"); 1106 mAutoFullScreenPending = true; 1107 mHandler.postDelayed(mAutoFullscreenRunnable, mAutoFullscreenTimeoutMillis); 1108 } 1109 1110 /** 1111 * Cancels pending auto fullscreen mode. 1112 */ 1113 public void cancelAutoFullScreen() { 1114 if (!mAutoFullScreenPending) { 1115 Log.v(this, "cancelAutoFullScreen : none pending."); 1116 return; 1117 } 1118 Log.v(this, "cancelAutoFullScreen : cancelling pending"); 1119 mAutoFullScreenPending = false; 1120 } 1121 1122 private static void updateCameraSelection(Call call) { 1123 Log.d(TAG, "updateCameraSelection: call=" + call); 1124 Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call)); 1125 1126 final Call activeCall = CallList.getInstance().getActiveCall(); 1127 int cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; 1128 1129 // this function should never be called with null call object, however if it happens we 1130 // should handle it gracefully. 1131 if (call == null) { 1132 cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; 1133 com.android.incallui.Log.e(TAG, "updateCameraSelection: Call object is null." 1134 + " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)"); 1135 } 1136 1137 // Clear camera direction if this is not a video call. 1138 else if (VideoUtils.isAudioCall(call)) { 1139 cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; 1140 call.getVideoSettings().setCameraDir(cameraDir); 1141 } 1142 1143 // If this is a waiting video call, default to active call's camera, 1144 // since we don't want to change the current camera for waiting call 1145 // without user's permission. 1146 else if (VideoUtils.isVideoCall(activeCall) && VideoUtils.isIncomingVideoCall(call)) { 1147 cameraDir = activeCall.getVideoSettings().getCameraDir(); 1148 } 1149 1150 // Infer the camera direction from the video state and store it, 1151 // if this is an outgoing video call. 1152 else if (VideoUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call) ) { 1153 cameraDir = toCameraDirection(call.getVideoState()); 1154 call.getVideoSettings().setCameraDir(cameraDir); 1155 } 1156 1157 // Use the stored camera dir if this is an outgoing video call for which camera direction 1158 // is set. 1159 else if (VideoUtils.isOutgoingVideoCall(call)) { 1160 cameraDir = call.getVideoSettings().getCameraDir(); 1161 } 1162 1163 // Infer the camera direction from the video state and store it, 1164 // if this is an active video call and camera direction is not set. 1165 else if (VideoUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) { 1166 cameraDir = toCameraDirection(call.getVideoState()); 1167 call.getVideoSettings().setCameraDir(cameraDir); 1168 } 1169 1170 // Use the stored camera dir if this is an active video call for which camera direction 1171 // is set. 1172 else if (VideoUtils.isActiveVideoCall(call)) { 1173 cameraDir = call.getVideoSettings().getCameraDir(); 1174 } 1175 1176 // For all other cases infer the camera direction but don't store it in the call object. 1177 else { 1178 cameraDir = toCameraDirection(call.getVideoState()); 1179 } 1180 1181 com.android.incallui.Log.d(TAG, "updateCameraSelection: Setting camera direction to " + 1182 cameraDir + " Call=" + call); 1183 final InCallCameraManager cameraManager = InCallPresenter.getInstance(). 1184 getInCallCameraManager(); 1185 cameraManager.setUseFrontFacingCamera(cameraDir == 1186 Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING); 1187 } 1188 1189 private static int toCameraDirection(int videoState) { 1190 return VideoProfile.isTransmissionEnabled(videoState) && 1191 !VideoProfile.isBidirectional(videoState) 1192 ? Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING 1193 : Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING; 1194 } 1195 1196 private static boolean isCameraDirectionSet(Call call) { 1197 return VideoUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir() 1198 != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN; 1199 } 1200 1201 private static String toSimpleString(Call call) { 1202 return call == null ? null : call.toSimpleString(); 1203 } 1204 1205 /** 1206 * Starts an asynchronous load of the user's profile photo. 1207 */ 1208 public void loadProfilePhotoAsync() { 1209 final VideoCallUi ui = getUi(); 1210 if (ui == null) { 1211 return; 1212 } 1213 1214 final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { 1215 /** 1216 * Performs asynchronous load of the user profile information. 1217 * 1218 * @param params The parameters of the task. 1219 * 1220 * @return {@code null}. 1221 */ 1222 @Override 1223 protected Void doInBackground(Void... params) { 1224 if (mProfileInfo == null) { 1225 // Try and read the photo URI from the local profile. 1226 mProfileInfo = new ContactInfoCache.ContactCacheEntry(); 1227 final Cursor cursor = mContext.getContentResolver().query( 1228 ContactsContract.Profile.CONTENT_URI, new String[]{ 1229 ContactsContract.CommonDataKinds.Phone._ID, 1230 ContactsContract.CommonDataKinds.Phone.PHOTO_URI, 1231 ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY, 1232 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, 1233 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_ALTERNATIVE 1234 }, null, null, null); 1235 if (cursor != null) { 1236 try { 1237 if (cursor.moveToFirst()) { 1238 mProfileInfo.lookupKey = cursor.getString(cursor.getColumnIndex( 1239 ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY)); 1240 String photoUri = cursor.getString(cursor.getColumnIndex( 1241 ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); 1242 mProfileInfo.displayPhotoUri = photoUri == null ? null 1243 : Uri.parse(photoUri); 1244 mProfileInfo.namePrimary = cursor.getString(cursor.getColumnIndex( 1245 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); 1246 mProfileInfo.nameAlternative = cursor.getString( 1247 cursor.getColumnIndex(ContactsContract.CommonDataKinds 1248 .Phone.DISPLAY_NAME_ALTERNATIVE)); 1249 } 1250 } finally { 1251 cursor.close(); 1252 } 1253 } 1254 } 1255 return null; 1256 } 1257 1258 @Override 1259 protected void onPostExecute(Void result) { 1260 // If user profile information was found, issue an async request to load the user's 1261 // profile photo. 1262 if (mProfileInfo != null) { 1263 if (mContactPhotoManager == null) { 1264 mContactPhotoManager = ContactPhotoManager.getInstance(mContext); 1265 } 1266 ContactPhotoManager.DefaultImageRequest imageRequest = (mProfileInfo != null) 1267 ? null : 1268 new ContactPhotoManager.DefaultImageRequest(mProfileInfo.namePrimary, 1269 mProfileInfo.lookupKey, false /* isCircularPhoto */); 1270 1271 ImageView photoView = ui.getPreviewPhotoView(); 1272 if (photoView == null) { 1273 return; 1274 } 1275 mContactPhotoManager.loadDirectoryPhoto(photoView, 1276 mProfileInfo.displayPhotoUri, 1277 false /* darkTheme */, false /* isCircular */, imageRequest); 1278 } 1279 } 1280 }; 1281 1282 task.execute(); 1283 } 1284 1285 /** 1286 * Defines the VideoCallUI interactions. 1287 */ 1288 public interface VideoCallUi extends Ui { 1289 void showVideoViews(boolean showPreview, boolean showIncoming); 1290 void hideVideoUi(); 1291 boolean isDisplayVideoSurfaceCreated(); 1292 boolean isPreviewVideoSurfaceCreated(); 1293 Surface getDisplayVideoSurface(); 1294 Surface getPreviewVideoSurface(); 1295 int getCurrentRotation(); 1296 void setPreviewSize(int width, int height); 1297 void setPreviewSurfaceSize(int width, int height); 1298 void setDisplayVideoSize(int width, int height); 1299 Point getScreenSize(); 1300 Point getPreviewSize(); 1301 void cleanupSurfaces(); 1302 ImageView getPreviewPhotoView(); 1303 void adjustPreviewLocation(boolean shiftUp, int offset); 1304 void setPreviewRotation(int orientation); 1305 } 1306 } 1307