1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.incallui; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.os.Handler; 22 import android.telecom.AudioState; 23 import android.telecom.CameraCapabilities; 24 import android.telecom.InCallService.VideoCall; 25 import android.view.Surface; 26 27 import com.android.contacts.common.CallUtil; 28 import com.android.incallui.InCallPresenter.InCallDetailsListener; 29 import com.android.incallui.InCallPresenter.InCallOrientationListener; 30 import com.android.incallui.InCallPresenter.InCallStateListener; 31 import com.android.incallui.InCallPresenter.IncomingCallListener; 32 import com.android.incallui.InCallVideoCallListenerNotifier.SurfaceChangeListener; 33 import com.android.incallui.InCallVideoCallListenerNotifier.VideoEventListener; 34 import com.google.common.base.Preconditions; 35 36 import java.util.Objects; 37 38 /** 39 * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling 40 * surfaces based on other user interface events and incoming events from the 41 * {@class VideoCallListener}. 42 * <p> 43 * When a call's video state changes to bi-directional video, the 44 * {@link com.android.incallui.VideoCallPresenter} performs the following negotiation with the 45 * telephony layer: 46 * <ul> 47 * <li>{@code VideoCallPresenter} creates and informs telephony of the display surface.</li> 48 * <li>{@code VideoCallPresenter} creates the preview surface.</li> 49 * <li>{@code VideoCallPresenter} informs telephony of the currently selected camera.</li> 50 * <li>Telephony layer sends {@link CameraCapabilities}, including the 51 * dimensions of the video for the current camera.</li> 52 * <li>{@code VideoCallPresenter} adjusts size of the preview surface to match the aspect 53 * ratio of the camera.</li> 54 * <li>{@code VideoCallPresenter} informs telephony of the new preview surface.</li> 55 * </ul> 56 * <p> 57 * When downgrading to an audio-only video state, the {@code VideoCallPresenter} nulls both 58 * surfaces. 59 */ 60 public class VideoCallPresenter extends Presenter<VideoCallPresenter.VideoCallUi> implements 61 IncomingCallListener, InCallOrientationListener, InCallStateListener, 62 InCallDetailsListener, SurfaceChangeListener, VideoEventListener, 63 InCallVideoCallListenerNotifier.SessionModificationListener { 64 65 /** 66 * Determines the device orientation (portrait/lanscape). 67 */ getDeviceOrientation()68 public int getDeviceOrientation() { 69 return mDeviceOrientation; 70 } 71 72 /** 73 * Defines the state of the preview surface negotiation with the telephony layer. 74 */ 75 private class PreviewSurfaceState { 76 /** 77 * The camera has not yet been set on the {@link VideoCall}; negotiation has not yet 78 * started. 79 */ 80 private static final int NONE = 0; 81 82 /** 83 * The camera has been set on the {@link VideoCall}, but camera capabilities have not yet 84 * been received. 85 */ 86 private static final int CAMERA_SET = 1; 87 88 /** 89 * The camera capabilties have been received from telephony, but the surface has not yet 90 * been set on the {@link VideoCall}. 91 */ 92 private static final int CAPABILITIES_RECEIVED = 2; 93 94 /** 95 * The surface has been set on the {@link VideoCall}. 96 */ 97 private static final int SURFACE_SET = 3; 98 } 99 100 /** 101 * The minimum width or height of the preview surface. Used when re-sizing the preview surface 102 * to match the aspect ratio of the currently selected camera. 103 */ 104 private float mMinimumVideoDimension; 105 106 /** 107 * The current context. 108 */ 109 private Context mContext; 110 111 /** 112 * The call the video surfaces are currently related to 113 */ 114 private Call mPrimaryCall; 115 116 /** 117 * The {@link VideoCall} used to inform the video telephony layer of changes to the video 118 * surfaces. 119 */ 120 private VideoCall mVideoCall; 121 122 /** 123 * Determines if the current UI state represents a video call. 124 */ 125 private boolean mIsVideoCall; 126 127 /** 128 * Determines the device orientation (portrait/lanscape). 129 */ 130 private int mDeviceOrientation; 131 132 /** 133 * Tracks the state of the preview surface negotiation with the telephony layer. 134 */ 135 private int mPreviewSurfaceState = PreviewSurfaceState.NONE; 136 137 /** 138 * Determines whether the video surface is in full-screen mode. 139 */ 140 private boolean mIsFullScreen = false; 141 142 /** 143 * Saves the audio mode which was selected prior to going into a video call. 144 */ 145 private int mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; 146 147 /** Handler which resets request state to NO_REQUEST after an interval. */ 148 private Handler mSessionModificationResetHandler; 149 private static final long SESSION_MODIFICATION_RESET_DELAY_MS = 3000; 150 151 /** 152 * Initializes the presenter. 153 * 154 * @param context The current context. 155 */ init(Context context)156 public void init(Context context) { 157 mContext = Preconditions.checkNotNull(context); 158 mMinimumVideoDimension = mContext.getResources().getDimension( 159 R.dimen.video_preview_small_dimension); 160 mSessionModificationResetHandler = new Handler(); 161 } 162 163 /** 164 * Called when the user interface is ready to be used. 165 * 166 * @param ui The Ui implementation that is now ready to be used. 167 */ 168 @Override onUiReady(VideoCallUi ui)169 public void onUiReady(VideoCallUi ui) { 170 super.onUiReady(ui); 171 172 // Register for call state changes last 173 InCallPresenter.getInstance().addListener(this); 174 InCallPresenter.getInstance().addIncomingCallListener(this); 175 InCallPresenter.getInstance().addOrientationListener(this); 176 177 // Register for surface and video events from {@link InCallVideoCallListener}s. 178 InCallVideoCallListenerNotifier.getInstance().addSurfaceChangeListener(this); 179 InCallVideoCallListenerNotifier.getInstance().addVideoEventListener(this); 180 InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this); 181 mIsVideoCall = false; 182 } 183 184 /** 185 * Called when the user interface is no longer ready to be used. 186 * 187 * @param ui The Ui implementation that is no longer ready to be used. 188 */ 189 @Override onUiUnready(VideoCallUi ui)190 public void onUiUnready(VideoCallUi ui) { 191 super.onUiUnready(ui); 192 193 InCallPresenter.getInstance().removeListener(this); 194 InCallPresenter.getInstance().removeIncomingCallListener(this); 195 InCallPresenter.getInstance().removeOrientationListener(this); 196 InCallVideoCallListenerNotifier.getInstance().removeSurfaceChangeListener(this); 197 InCallVideoCallListenerNotifier.getInstance().removeVideoEventListener(this); 198 InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this); 199 } 200 201 /** 202 * @return The {@link VideoCall}. 203 */ getVideoCall()204 private VideoCall getVideoCall() { 205 return mVideoCall; 206 } 207 208 /** 209 * Handles the creation of a surface in the {@link VideoCallFragment}. 210 * 211 * @param surface The surface which was created. 212 */ onSurfaceCreated(int surface)213 public void onSurfaceCreated(int surface) { 214 final VideoCallUi ui = getUi(); 215 216 if (ui == null || mVideoCall == null) { 217 return; 218 } 219 220 // If the preview surface has just been created and we have already received camera 221 // capabilities, but not yet set the surface, we will set the surface now. 222 if (surface == VideoCallFragment.SURFACE_PREVIEW && 223 mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) { 224 225 mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; 226 mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); 227 } else if (surface == VideoCallFragment.SURFACE_DISPLAY) { 228 mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); 229 } 230 } 231 232 /** 233 * Handles structural changes (format or size) to a surface. 234 * 235 * @param surface The surface which changed. 236 * @param format The new PixelFormat of the surface. 237 * @param width The new width of the surface. 238 * @param height The new height of the surface. 239 */ onSurfaceChanged(int surface, int format, int width, int height)240 public void onSurfaceChanged(int surface, int format, int width, int height) { 241 //Do stuff 242 } 243 244 /** 245 * Handles the destruction of a surface in the {@link VideoCallFragment}. 246 * 247 * @param surface The surface which was destroyed. 248 */ onSurfaceDestroyed(int surface)249 public void onSurfaceDestroyed(int surface) { 250 final VideoCallUi ui = getUi(); 251 if (ui == null || mVideoCall == null) { 252 return; 253 } 254 255 if (surface == VideoCallFragment.SURFACE_DISPLAY) { 256 mVideoCall.setDisplaySurface(null); 257 } else if (surface == VideoCallFragment.SURFACE_PREVIEW) { 258 mVideoCall.setPreviewSurface(null); 259 } 260 } 261 262 /** 263 * Handles clicks on the video surfaces by toggling full screen state. 264 * Informs the {@link InCallPresenter} of the change so that it can inform the 265 * {@link CallCardPresenter} of the change. 266 * 267 * @param surfaceId The video surface receiving the click. 268 */ onSurfaceClick(int surfaceId)269 public void onSurfaceClick(int surfaceId) { 270 mIsFullScreen = !mIsFullScreen; 271 InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen); 272 } 273 274 275 /** 276 * Handles incoming calls. 277 * 278 * @param state The in call state. 279 * @param call The call. 280 */ 281 @Override onIncomingCall(InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, Call call)282 public void onIncomingCall(InCallPresenter.InCallState oldState, 283 InCallPresenter.InCallState newState, Call call) { 284 // same logic should happen as with onStateChange() 285 onStateChange(oldState, newState, CallList.getInstance()); 286 } 287 288 /** 289 * Handles state changes (including incoming calls) 290 * 291 * @param newState The in call state. 292 * @param callList The call list. 293 */ 294 @Override onStateChange(InCallPresenter.InCallState oldState, InCallPresenter.InCallState newState, CallList callList)295 public void onStateChange(InCallPresenter.InCallState oldState, 296 InCallPresenter.InCallState newState, CallList callList) { 297 // Bail if video calling is disabled for the device. 298 if (!CallUtil.isVideoEnabled(mContext)) { 299 return; 300 } 301 302 if (newState == InCallPresenter.InCallState.NO_CALLS) { 303 exitVideoMode(); 304 } 305 306 // Determine the primary active call). 307 Call primary = null; 308 if (newState == InCallPresenter.InCallState.INCOMING) { 309 primary = callList.getIncomingCall(); 310 } else if (newState == InCallPresenter.InCallState.OUTGOING) { 311 primary = callList.getOutgoingCall(); 312 } else if (newState == InCallPresenter.InCallState.INCALL) { 313 primary = callList.getActiveCall(); 314 } 315 316 final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary); 317 if (primaryChanged) { 318 mPrimaryCall = primary; 319 320 if (primary != null) { 321 checkForVideoCallChange(); 322 mIsVideoCall = mPrimaryCall.isVideoCall(mContext); 323 if (mIsVideoCall) { 324 enterVideoMode(); 325 } else { 326 exitVideoMode(); 327 } 328 } else if (primary == null) { 329 // If no primary call, ensure we exit video state and clean up the video surfaces. 330 exitVideoMode(); 331 } 332 } 333 } 334 335 /** 336 * Handles changes to the details of the call. The {@link VideoCallPresenter} is interested in 337 * changes to the video state. 338 * 339 * @param call The call for which the details changed. 340 * @param details The new call details. 341 */ 342 @Override onDetailsChanged(Call call, android.telecom.Call.Details details)343 public void onDetailsChanged(Call call, android.telecom.Call.Details details) { 344 // If the details change is not for the currently active call no update is required. 345 if (!call.equals(mPrimaryCall)) { 346 return; 347 } 348 349 checkForVideoStateChange(); 350 } 351 352 /** 353 * Checks for a change to the video call and changes it if required. 354 */ checkForVideoCallChange()355 private void checkForVideoCallChange() { 356 VideoCall videoCall = mPrimaryCall.getTelecommCall().getVideoCall(); 357 if (!Objects.equals(videoCall, mVideoCall)) { 358 changeVideoCall(videoCall); 359 } 360 } 361 362 /** 363 * Checks to see if the current video state has changed and updates the UI if required. 364 */ checkForVideoStateChange()365 private void checkForVideoStateChange() { 366 boolean newVideoState = mPrimaryCall.isVideoCall(mContext); 367 368 // Check if video state changed 369 if (mIsVideoCall != newVideoState) { 370 mIsVideoCall = newVideoState; 371 372 if (mIsVideoCall) { 373 enterVideoMode(); 374 } else { 375 exitVideoMode(); 376 } 377 } 378 } 379 380 /** 381 * Handles a change to the video call. Sets the surfaces on the previous call to null and sets 382 * the surfaces on the new video call accordingly. 383 * 384 * @param videoCall The new video call. 385 */ changeVideoCall(VideoCall videoCall)386 private void changeVideoCall(VideoCall videoCall) { 387 // Null out the surfaces on the previous video call. 388 if (mVideoCall != null) { 389 mVideoCall.setDisplaySurface(null); 390 mVideoCall.setPreviewSurface(null); 391 } 392 393 mVideoCall = videoCall; 394 } 395 396 /** 397 * Enters video mode by showing the video surfaces and making other adjustments (eg. audio). 398 * TODO(vt): Need to adjust size and orientation of preview surface here. 399 */ enterVideoMode()400 private void enterVideoMode() { 401 VideoCallUi ui = getUi(); 402 if (ui == null) { 403 return; 404 } 405 406 ui.showVideoUi(true); 407 InCallPresenter.getInstance().setInCallAllowsOrientationChange(true); 408 409 // Communicate the current camera to telephony and make a request for the camera 410 // capabilities. 411 if (mVideoCall != null) { 412 // Do not reset the surfaces if we just restarted the activity due to an orientation 413 // change. 414 if (ui.isActivityRestart()) { 415 return; 416 } 417 418 mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET; 419 InCallCameraManager cameraManager = InCallPresenter.getInstance(). 420 getInCallCameraManager(); 421 mVideoCall.setCamera(cameraManager.getActiveCameraId()); 422 mVideoCall.requestCameraCapabilities(); 423 424 if (ui.isDisplayVideoSurfaceCreated()) { 425 mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface()); 426 } 427 } 428 429 mPreVideoAudioMode = AudioModeProvider.getInstance().getAudioMode(); 430 TelecomAdapter.getInstance().setAudioRoute(AudioState.ROUTE_SPEAKER); 431 } 432 433 /** 434 * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio). 435 */ exitVideoMode()436 private void exitVideoMode() { 437 VideoCallUi ui = getUi(); 438 if (ui == null) { 439 return; 440 } 441 InCallPresenter.getInstance().setInCallAllowsOrientationChange(false); 442 ui.showVideoUi(false); 443 444 if (mPreVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID) { 445 TelecomAdapter.getInstance().setAudioRoute(mPreVideoAudioMode); 446 mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID; 447 } 448 } 449 450 /** 451 * Handles peer video pause state changes. 452 * 453 * @param call The call which paused or un-pausedvideo transmission. 454 * @param paused {@code True} when the video transmission is paused, {@code false} when video 455 * transmission resumes. 456 */ 457 @Override onPeerPauseStateChanged(Call call, boolean paused)458 public void onPeerPauseStateChanged(Call call, boolean paused) { 459 if (!call.equals(mPrimaryCall)) { 460 return; 461 } 462 463 // TODO(vt): Show/hide the peer contact photo. 464 } 465 466 /** 467 * Handles peer video dimension changes. 468 * 469 * @param call The call which experienced a peer video dimension change. 470 * @param width The new peer video width . 471 * @param height The new peer video height. 472 */ 473 @Override onUpdatePeerDimensions(Call call, int width, int height)474 public void onUpdatePeerDimensions(Call call, int width, int height) { 475 if (!call.equals(mPrimaryCall)) { 476 return; 477 } 478 479 // TODO(vt): Change display surface aspect ratio. 480 } 481 482 /** 483 * Handles a change to the dimensions of the local camera. Receiving the camera capabilities 484 * triggers the creation of the video 485 * 486 * @param call The call which experienced the camera dimension change. 487 * @param width The new camera video width. 488 * @param height The new camera video height. 489 */ 490 @Override onCameraDimensionsChange(Call call, int width, int height)491 public void onCameraDimensionsChange(Call call, int width, int height) { 492 VideoCallUi ui = getUi(); 493 if (ui == null) { 494 return; 495 } 496 497 if (!call.equals(mPrimaryCall)) { 498 return; 499 } 500 501 mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED; 502 503 // Configure the preview surface to the correct aspect ratio. 504 float aspectRatio = 1.0f; 505 if (width > 0 && height > 0) { 506 aspectRatio = (float) width / (float) height; 507 } 508 setPreviewSize(mDeviceOrientation, aspectRatio); 509 510 // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}. 511 // If it not yet ready, it will be set when when creation completes. 512 if (ui.isPreviewVideoSurfaceCreated()) { 513 mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET; 514 mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface()); 515 } 516 } 517 518 /** 519 * Handles hanges to the device orientation. 520 * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT} 521 * @param orientation The device orientation. 522 */ 523 @Override onDeviceOrientationChanged(int orientation)524 public void onDeviceOrientationChanged(int orientation) { 525 mDeviceOrientation = orientation; 526 } 527 528 @Override onUpgradeToVideoRequest(Call call)529 public void onUpgradeToVideoRequest(Call call) { 530 mPrimaryCall.setSessionModificationState( 531 Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST); 532 } 533 534 @Override onUpgradeToVideoSuccess(Call call)535 public void onUpgradeToVideoSuccess(Call call) { 536 if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { 537 return; 538 } 539 540 mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); 541 } 542 543 @Override onUpgradeToVideoFail(Call call)544 public void onUpgradeToVideoFail(Call call) { 545 if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) { 546 return; 547 } 548 549 call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED); 550 551 // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval. 552 mSessionModificationResetHandler.postDelayed(new Runnable() { 553 @Override 554 public void run() { 555 mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); 556 } 557 }, SESSION_MODIFICATION_RESET_DELAY_MS); 558 } 559 560 @Override onDowngradeToAudio(Call call)561 public void onDowngradeToAudio(Call call) { 562 // Implementing to satsify interface. 563 } 564 565 /** 566 * Sets the preview surface size based on the current device orientation. 567 * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT} 568 * 569 * @param orientation The device orientation. 570 * @param aspectRatio The aspect ratio of the camera (width / height). 571 */ setPreviewSize(int orientation, float aspectRatio)572 private void setPreviewSize(int orientation, float aspectRatio) { 573 VideoCallUi ui = getUi(); 574 if (ui == null) { 575 return; 576 } 577 578 int height; 579 int width; 580 581 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 582 width = (int) (mMinimumVideoDimension * aspectRatio); 583 height = (int) mMinimumVideoDimension; 584 } else { 585 width = (int) mMinimumVideoDimension; 586 height = (int) (mMinimumVideoDimension * aspectRatio); 587 } 588 ui.setPreviewSize(width, height); 589 } 590 591 /** 592 * Defines the VideoCallUI interactions. 593 */ 594 public interface VideoCallUi extends Ui { showVideoUi(boolean show)595 void showVideoUi(boolean show); isDisplayVideoSurfaceCreated()596 boolean isDisplayVideoSurfaceCreated(); isPreviewVideoSurfaceCreated()597 boolean isPreviewVideoSurfaceCreated(); getDisplayVideoSurface()598 Surface getDisplayVideoSurface(); getPreviewVideoSurface()599 Surface getPreviewVideoSurface(); setPreviewSize(int width, int height)600 void setPreviewSize(int width, int height); cleanupSurfaces()601 void cleanupSurfaces(); isActivityRestart()602 boolean isActivityRestart(); 603 } 604 } 605