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