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