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.ims.internal; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.net.Uri; 21 import android.os.Binder; 22 import android.os.Handler; 23 import android.os.IBinder; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.RegistrantList; 27 import android.os.RemoteException; 28 import android.telecom.Connection; 29 import android.telecom.VideoProfile; 30 import android.util.Log; 31 import android.view.Surface; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.os.SomeArgs; 35 36 import java.util.Collections; 37 import java.util.NoSuchElementException; 38 import java.util.Set; 39 import java.util.concurrent.ConcurrentHashMap; 40 41 /** 42 * Subclass implementation of {@link Connection.VideoProvider}. This intermediates and 43 * communicates with the actual implementation of the video call provider in the IMS service; it is 44 * in essence, a wrapper around the IMS's video call provider implementation. 45 * 46 * This class maintains a binder by which the ImsVideoCallProvider's implementation can communicate 47 * its intent to invoke callbacks. In this class, the message across this binder is handled, and 48 * the superclass's methods are used to execute the callbacks. 49 * 50 * @hide 51 */ 52 public class ImsVideoCallProviderWrapper extends Connection.VideoProvider { 53 54 public interface ImsVideoProviderWrapperCallback { onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)55 void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, 56 VideoProfile responseProfile); 57 } 58 59 private static final String LOG_TAG = ImsVideoCallProviderWrapper.class.getSimpleName(); 60 61 private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1; 62 private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2; 63 private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3; 64 private static final int MSG_CHANGE_PEER_DIMENSIONS = 4; 65 private static final int MSG_CHANGE_CALL_DATA_USAGE = 5; 66 private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6; 67 private static final int MSG_CHANGE_VIDEO_QUALITY = 7; 68 69 private final IImsVideoCallProvider mVideoCallProvider; 70 private final ImsVideoCallCallback mBinder; 71 private RegistrantList mDataUsageUpdateRegistrants = new RegistrantList(); 72 private final Set<ImsVideoProviderWrapperCallback> mCallbacks = Collections.newSetFromMap( 73 new ConcurrentHashMap<ImsVideoProviderWrapperCallback, Boolean>(8, 0.9f, 1)); 74 private VideoPauseTracker mVideoPauseTracker = new VideoPauseTracker(); 75 private boolean mUseVideoPauseWorkaround = false; 76 private int mCurrentVideoState; 77 private boolean mIsVideoEnabled = true; 78 79 private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { 80 @Override 81 public void binderDied() { 82 try { 83 mVideoCallProvider.asBinder().unlinkToDeath(this, 0); 84 } catch (NoSuchElementException nse) { 85 // Already unlinked, potentially below in tearDown. 86 } 87 } 88 }; 89 90 /** 91 * IImsVideoCallCallback stub implementation. 92 */ 93 private final class ImsVideoCallCallback extends IImsVideoCallCallback.Stub { 94 @Override receiveSessionModifyRequest(VideoProfile VideoProfile)95 public void receiveSessionModifyRequest(VideoProfile VideoProfile) { 96 mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_REQUEST, 97 VideoProfile).sendToTarget(); 98 } 99 100 @Override receiveSessionModifyResponse( int status, VideoProfile requestProfile, VideoProfile responseProfile)101 public void receiveSessionModifyResponse( 102 int status, VideoProfile requestProfile, VideoProfile responseProfile) { 103 SomeArgs args = SomeArgs.obtain(); 104 args.arg1 = status; 105 args.arg2 = requestProfile; 106 args.arg3 = responseProfile; 107 mHandler.obtainMessage(MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args).sendToTarget(); 108 } 109 110 @Override handleCallSessionEvent(int event)111 public void handleCallSessionEvent(int event) { 112 mHandler.obtainMessage(MSG_HANDLE_CALL_SESSION_EVENT, event).sendToTarget(); 113 } 114 115 @Override changePeerDimensions(int width, int height)116 public void changePeerDimensions(int width, int height) { 117 SomeArgs args = SomeArgs.obtain(); 118 args.arg1 = width; 119 args.arg2 = height; 120 mHandler.obtainMessage(MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget(); 121 } 122 123 @Override changeVideoQuality(int videoQuality)124 public void changeVideoQuality(int videoQuality) { 125 mHandler.obtainMessage(MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0).sendToTarget(); 126 } 127 128 @Override changeCallDataUsage(long dataUsage)129 public void changeCallDataUsage(long dataUsage) { 130 mHandler.obtainMessage(MSG_CHANGE_CALL_DATA_USAGE, dataUsage).sendToTarget(); 131 } 132 133 @Override changeCameraCapabilities( VideoProfile.CameraCapabilities cameraCapabilities)134 public void changeCameraCapabilities( 135 VideoProfile.CameraCapabilities cameraCapabilities) { 136 mHandler.obtainMessage(MSG_CHANGE_CAMERA_CAPABILITIES, 137 cameraCapabilities).sendToTarget(); 138 } 139 } 140 registerForDataUsageUpdate(Handler h, int what, Object obj)141 public void registerForDataUsageUpdate(Handler h, int what, Object obj) { 142 mDataUsageUpdateRegistrants.addUnique(h, what, obj); 143 } 144 unregisterForDataUsageUpdate(Handler h)145 public void unregisterForDataUsageUpdate(Handler h) { 146 mDataUsageUpdateRegistrants.remove(h); 147 } 148 addImsVideoProviderCallback(ImsVideoProviderWrapperCallback callback)149 public void addImsVideoProviderCallback(ImsVideoProviderWrapperCallback callback) { 150 mCallbacks.add(callback); 151 } 152 removeImsVideoProviderCallback(ImsVideoProviderWrapperCallback callback)153 public void removeImsVideoProviderCallback(ImsVideoProviderWrapperCallback callback) { 154 mCallbacks.remove(callback); 155 } 156 157 /** Default handler used to consolidate binder method calls onto a single thread. */ 158 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 159 @Override 160 public void handleMessage(Message msg) { 161 SomeArgs args; 162 switch (msg.what) { 163 case MSG_RECEIVE_SESSION_MODIFY_REQUEST: { 164 VideoProfile videoProfile = (VideoProfile) msg.obj; 165 if (!VideoProfile.isVideo(mCurrentVideoState) && VideoProfile.isVideo( 166 videoProfile.getVideoState()) && !mIsVideoEnabled) { 167 // Video is disabled, reject the request. 168 Log.i(LOG_TAG, String.format( 169 "receiveSessionModifyRequest: requestedVideoState=%s; rejecting " 170 + "as video is disabled.", 171 videoProfile.getVideoState())); 172 try { 173 mVideoCallProvider.sendSessionModifyResponse( 174 new VideoProfile(VideoProfile.STATE_AUDIO_ONLY)); 175 } catch (RemoteException e) { 176 } 177 return; 178 } 179 receiveSessionModifyRequest(videoProfile); 180 } 181 break; 182 case MSG_RECEIVE_SESSION_MODIFY_RESPONSE: 183 args = (SomeArgs) msg.obj; 184 try { 185 int status = (int) args.arg1; 186 VideoProfile requestProfile = (VideoProfile) args.arg2; 187 VideoProfile responseProfile = (VideoProfile) args.arg3; 188 189 receiveSessionModifyResponse(status, requestProfile, responseProfile); 190 191 // Notify any local Telephony components interested in upgrade responses. 192 for (ImsVideoProviderWrapperCallback callback : mCallbacks) { 193 if (callback != null) { 194 callback.onReceiveSessionModifyResponse(status, requestProfile, 195 responseProfile); 196 } 197 } 198 } finally { 199 args.recycle(); 200 } 201 break; 202 case MSG_HANDLE_CALL_SESSION_EVENT: 203 handleCallSessionEvent((int) msg.obj); 204 break; 205 case MSG_CHANGE_PEER_DIMENSIONS: 206 args = (SomeArgs) msg.obj; 207 try { 208 int width = (int) args.arg1; 209 int height = (int) args.arg2; 210 changePeerDimensions(width, height); 211 } finally { 212 args.recycle(); 213 } 214 break; 215 case MSG_CHANGE_CALL_DATA_USAGE: 216 // TODO: We should use callback in the future. 217 setCallDataUsage((long) msg.obj); 218 mDataUsageUpdateRegistrants.notifyResult(msg.obj); 219 break; 220 case MSG_CHANGE_CAMERA_CAPABILITIES: 221 changeCameraCapabilities((VideoProfile.CameraCapabilities) msg.obj); 222 break; 223 case MSG_CHANGE_VIDEO_QUALITY: 224 changeVideoQuality(msg.arg1); 225 break; 226 default: 227 break; 228 } 229 } 230 }; 231 232 /** 233 * Instantiates an instance of the ImsVideoCallProvider, taking in the binder for IMS's video 234 * call provider implementation. 235 * 236 * @param videoProvider 237 */ 238 @UnsupportedAppUsage ImsVideoCallProviderWrapper(IImsVideoCallProvider videoProvider)239 public ImsVideoCallProviderWrapper(IImsVideoCallProvider videoProvider) 240 throws RemoteException { 241 242 mVideoCallProvider = videoProvider; 243 if (videoProvider != null) { 244 mVideoCallProvider.asBinder().linkToDeath(mDeathRecipient, 0); 245 246 mBinder = new ImsVideoCallCallback(); 247 mVideoCallProvider.setCallback(mBinder); 248 } else { 249 mBinder = null; 250 } 251 } 252 253 @VisibleForTesting ImsVideoCallProviderWrapper(IImsVideoCallProvider videoProvider, VideoPauseTracker videoPauseTracker)254 public ImsVideoCallProviderWrapper(IImsVideoCallProvider videoProvider, 255 VideoPauseTracker videoPauseTracker) 256 throws RemoteException { 257 this(videoProvider); 258 mVideoPauseTracker = videoPauseTracker; 259 } 260 261 /** @inheritDoc */ onSetCamera(String cameraId)262 public void onSetCamera(String cameraId) { 263 try { 264 mVideoCallProvider.setCamera(cameraId, Binder.getCallingUid()); 265 } catch (RemoteException e) { 266 } 267 } 268 269 /** @inheritDoc */ onSetPreviewSurface(Surface surface)270 public void onSetPreviewSurface(Surface surface) { 271 try { 272 mVideoCallProvider.setPreviewSurface(surface); 273 } catch (RemoteException e) { 274 } 275 } 276 277 /** @inheritDoc */ onSetDisplaySurface(Surface surface)278 public void onSetDisplaySurface(Surface surface) { 279 try { 280 mVideoCallProvider.setDisplaySurface(surface); 281 } catch (RemoteException e) { 282 } 283 } 284 285 /** @inheritDoc */ onSetDeviceOrientation(int rotation)286 public void onSetDeviceOrientation(int rotation) { 287 try { 288 mVideoCallProvider.setDeviceOrientation(rotation); 289 } catch (RemoteException e) { 290 } 291 } 292 293 /** @inheritDoc */ onSetZoom(float value)294 public void onSetZoom(float value) { 295 try { 296 mVideoCallProvider.setZoom(value); 297 } catch (RemoteException e) { 298 } 299 } 300 301 /** 302 * Handles session modify requests received from the {@link android.telecom.InCallService}. 303 * 304 * @inheritDoc 305 **/ onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile)306 public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) { 307 if (fromProfile == null || toProfile == null) { 308 Log.w(LOG_TAG, "onSendSessionModifyRequest: null profile in request."); 309 return; 310 } 311 312 try { 313 if (isResumeRequest(fromProfile.getVideoState(), toProfile.getVideoState()) && 314 !VideoProfile.isPaused(mCurrentVideoState)) { 315 // Request is to resume, but we're already resumed so ignore the request. 316 Log.i(LOG_TAG, String.format( 317 "onSendSessionModifyRequest: fromVideoState=%s, toVideoState=%s; " 318 + "skipping resume request - already resumed.", 319 VideoProfile.videoStateToString(fromProfile.getVideoState()), 320 VideoProfile.videoStateToString(toProfile.getVideoState()))); 321 return; 322 } 323 324 toProfile = maybeFilterPauseResume(fromProfile, toProfile, 325 VideoPauseTracker.SOURCE_INCALL); 326 327 int fromVideoState = fromProfile.getVideoState(); 328 int toVideoState = toProfile.getVideoState(); 329 Log.i(LOG_TAG, String.format( 330 "onSendSessionModifyRequest: fromVideoState=%s, toVideoState=%s; ", 331 VideoProfile.videoStateToString(fromProfile.getVideoState()), 332 VideoProfile.videoStateToString(toProfile.getVideoState()))); 333 mVideoCallProvider.sendSessionModifyRequest(fromProfile, toProfile); 334 } catch (RemoteException e) { 335 } 336 } 337 338 /** @inheritDoc */ onSendSessionModifyResponse(VideoProfile responseProfile)339 public void onSendSessionModifyResponse(VideoProfile responseProfile) { 340 try { 341 mVideoCallProvider.sendSessionModifyResponse(responseProfile); 342 } catch (RemoteException e) { 343 } 344 } 345 346 /** @inheritDoc */ onRequestCameraCapabilities()347 public void onRequestCameraCapabilities() { 348 try { 349 mVideoCallProvider.requestCameraCapabilities(); 350 } catch (RemoteException e) { 351 } 352 } 353 354 /** @inheritDoc */ onRequestConnectionDataUsage()355 public void onRequestConnectionDataUsage() { 356 try { 357 mVideoCallProvider.requestCallDataUsage(); 358 } catch (RemoteException e) { 359 } 360 } 361 362 /** @inheritDoc */ onSetPauseImage(Uri uri)363 public void onSetPauseImage(Uri uri) { 364 try { 365 mVideoCallProvider.setPauseImage(uri); 366 } catch (RemoteException e) { 367 } 368 } 369 370 /** 371 * Determines if a session modify request represents a request to pause the video. 372 * 373 * @param from The from video state. 374 * @param to The to video state. 375 * @return {@code true} if a pause was requested. 376 */ 377 @VisibleForTesting isPauseRequest(int from, int to)378 public static boolean isPauseRequest(int from, int to) { 379 boolean fromPaused = VideoProfile.isPaused(from); 380 boolean toPaused = VideoProfile.isPaused(to); 381 382 return !fromPaused && toPaused; 383 } 384 385 /** 386 * Determines if a session modify request represents a request to resume the video. 387 * 388 * @param from The from video state. 389 * @param to The to video state. 390 * @return {@code true} if a resume was requested. 391 */ 392 @VisibleForTesting isResumeRequest(int from, int to)393 public static boolean isResumeRequest(int from, int to) { 394 boolean fromPaused = VideoProfile.isPaused(from); 395 boolean toPaused = VideoProfile.isPaused(to); 396 397 return fromPaused && !toPaused; 398 } 399 400 /** 401 * Determines if this request includes turning the camera off (ie turning off transmission). 402 * @param from the from video state. 403 * @param to the to video state. 404 * @return true if the state change disables the user's camera. 405 */ 406 @VisibleForTesting isTurnOffCameraRequest(int from, int to)407 public static boolean isTurnOffCameraRequest(int from, int to) { 408 return VideoProfile.isTransmissionEnabled(from) 409 && !VideoProfile.isTransmissionEnabled(to); 410 } 411 412 /** 413 * Determines if this request includes turning the camera on (ie turning on transmission). 414 * @param from the from video state. 415 * @param to the to video state. 416 * @return true if the state change enables the user's camera. 417 */ 418 @VisibleForTesting isTurnOnCameraRequest(int from, int to)419 public static boolean isTurnOnCameraRequest(int from, int to) { 420 return !VideoProfile.isTransmissionEnabled(from) 421 && VideoProfile.isTransmissionEnabled(to); 422 } 423 424 /** 425 * Filters incoming pause and resume requests based on whether there are other active pause or 426 * resume requests at the current time. 427 * 428 * Requests to pause the video stream using the {@link VideoProfile#STATE_PAUSED} bit can come 429 * from both the {@link android.telecom.InCallService}, as well as via the 430 * {@link #pauseVideo(int, int)} and {@link #resumeVideo(int, int)} methods. As a result, 431 * multiple sources can potentially pause or resume the video stream. This method ensures that 432 * providing any one request source has paused the video that the video will remain paused. 433 * 434 * @param fromProfile The request's from {@link VideoProfile}. 435 * @param toProfile The request's to {@link VideoProfile}. 436 * @param source The source of the request, as identified by a {@code VideoPauseTracker#SOURCE*} 437 * constant. 438 * @return The new toProfile, with the pause bit set or unset based on whether we should 439 * actually pause or resume the video at the current time. 440 */ 441 @VisibleForTesting maybeFilterPauseResume(VideoProfile fromProfile, VideoProfile toProfile, int source)442 public VideoProfile maybeFilterPauseResume(VideoProfile fromProfile, VideoProfile toProfile, 443 int source) { 444 int fromVideoState = fromProfile.getVideoState(); 445 int toVideoState = toProfile.getVideoState(); 446 447 // TODO: Remove the following workaround in favor of a new API. 448 // The current sendSessionModifyRequest API has a flaw. If the video is already 449 // paused, it is not possible for the IncallService to inform the VideoProvider that 450 // it wishes to pause due to multi-tasking. 451 // In a future release we should add a new explicity pauseVideo and resumeVideo API 452 // instead of a difference between two video states. 453 // For now, we'll assume if the request is from pause to pause, we'll still try to 454 // pause. 455 boolean isPauseSpecialCase = (source == VideoPauseTracker.SOURCE_INCALL && 456 VideoProfile.isPaused(fromVideoState) && 457 VideoProfile.isPaused(toVideoState)); 458 459 boolean isPauseRequest = isPauseRequest(fromVideoState, toVideoState) || isPauseSpecialCase; 460 boolean isResumeRequest = isResumeRequest(fromVideoState, toVideoState); 461 if (isPauseRequest) { 462 Log.i(LOG_TAG, String.format("maybeFilterPauseResume: isPauseRequest (from=%s, to=%s)", 463 VideoProfile.videoStateToString(fromVideoState), 464 VideoProfile.videoStateToString(toVideoState))); 465 // Check if we have already paused the video in the past. 466 if (!mVideoPauseTracker.shouldPauseVideoFor(source) && !isPauseSpecialCase) { 467 // Note: We don't want to remove the "pause" in the "special case" scenario. If we 468 // do the resulting request will be from PAUSED --> UNPAUSED, which would resume the 469 // video. 470 471 // Video was already paused, so remove the pause in the "to" profile. 472 toVideoState = toVideoState & ~VideoProfile.STATE_PAUSED; 473 toProfile = new VideoProfile(toVideoState, toProfile.getQuality()); 474 } 475 } else if (isResumeRequest) { 476 boolean isTurnOffCameraRequest = isTurnOffCameraRequest(fromVideoState, toVideoState); 477 boolean isTurnOnCameraRequest = isTurnOnCameraRequest(fromVideoState, toVideoState); 478 // TODO: Fix vendor code so that this isn't required. 479 // Some vendors do not properly handle turning the camera on/off when the video is 480 // in paused state. 481 // If the request is to turn on/off the camera, it might be in the unfortunate format: 482 // FROM: Audio Tx Rx Pause TO: Audio Rx 483 // FROM: Audio Rx Pause TO: Audio Rx Tx 484 // If this is the case, we should not treat this request as a resume request as well. 485 // Ideally the IMS stack should treat a turn off camera request as: 486 // FROM: Audio Tx Rx Pause TO: Audio Rx Pause 487 // FROM: Audio Rx Pause TO: Audio Rx Tx Pause 488 // Unfortunately, it does not. ¯\_(ツ)_/¯ 489 if (mUseVideoPauseWorkaround && (isTurnOffCameraRequest || isTurnOnCameraRequest)) { 490 Log.i(LOG_TAG, String.format("maybeFilterPauseResume: isResumeRequest," 491 + " but camera turning on/off so skipping (from=%s, to=%s)", 492 VideoProfile.videoStateToString(fromVideoState), 493 VideoProfile.videoStateToString(toVideoState))); 494 return toProfile; 495 } 496 Log.i(LOG_TAG, String.format("maybeFilterPauseResume: isResumeRequest (from=%s, to=%s)", 497 VideoProfile.videoStateToString(fromVideoState), 498 VideoProfile.videoStateToString(toVideoState))); 499 // Check if we should remain paused (other pause requests pending). 500 if (!mVideoPauseTracker.shouldResumeVideoFor(source)) { 501 // There are other pause requests from other sources which are still active, so we 502 // should remain paused. 503 toVideoState = toVideoState | VideoProfile.STATE_PAUSED; 504 toProfile = new VideoProfile(toVideoState, toProfile.getQuality()); 505 } 506 } 507 508 return toProfile; 509 } 510 511 /** 512 * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source 513 * other than the InCall UI. 514 * 515 * @param fromVideoState The current video state (prior to issuing the pause). 516 * @param source The source of the pause request. 517 */ pauseVideo(int fromVideoState, int source)518 public void pauseVideo(int fromVideoState, int source) { 519 if (mVideoPauseTracker.shouldPauseVideoFor(source)) { 520 // We should pause the video (its not already paused). 521 VideoProfile fromProfile = new VideoProfile(fromVideoState); 522 VideoProfile toProfile = new VideoProfile(fromVideoState | VideoProfile.STATE_PAUSED); 523 524 try { 525 Log.i(LOG_TAG, String.format("pauseVideo: fromVideoState=%s, toVideoState=%s", 526 VideoProfile.videoStateToString(fromProfile.getVideoState()), 527 VideoProfile.videoStateToString(toProfile.getVideoState()))); 528 mVideoCallProvider.sendSessionModifyRequest(fromProfile, toProfile); 529 } catch (RemoteException e) { 530 } 531 } else { 532 Log.i(LOG_TAG, "pauseVideo: video already paused"); 533 } 534 } 535 536 /** 537 * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source 538 * other than the InCall UI. 539 * 540 * @param fromVideoState The current video state (prior to issuing the resume). 541 * @param source The source of the resume request. 542 */ resumeVideo(int fromVideoState, int source)543 public void resumeVideo(int fromVideoState, int source) { 544 if (mVideoPauseTracker.shouldResumeVideoFor(source)) { 545 // We are the last source to resume, so resume now. 546 VideoProfile fromProfile = new VideoProfile(fromVideoState); 547 VideoProfile toProfile = new VideoProfile(fromVideoState & ~VideoProfile.STATE_PAUSED); 548 549 try { 550 Log.i(LOG_TAG, String.format("resumeVideo: fromVideoState=%s, toVideoState=%s", 551 VideoProfile.videoStateToString(fromProfile.getVideoState()), 552 VideoProfile.videoStateToString(toProfile.getVideoState()))); 553 mVideoCallProvider.sendSessionModifyRequest(fromProfile, toProfile); 554 } catch (RemoteException e) { 555 } 556 } else { 557 Log.i(LOG_TAG, "resumeVideo: remaining paused (paused from other sources)"); 558 } 559 } 560 561 /** 562 * Determines if a specified source has issued a pause request. 563 * 564 * @param source The source. 565 * @return {@code true} if the source issued a pause request, {@code false} otherwise. 566 */ wasVideoPausedFromSource(int source)567 public boolean wasVideoPausedFromSource(int source) { 568 return mVideoPauseTracker.wasVideoPausedFromSource(source); 569 } 570 setUseVideoPauseWorkaround(boolean useVideoPauseWorkaround)571 public void setUseVideoPauseWorkaround(boolean useVideoPauseWorkaround) { 572 mUseVideoPauseWorkaround = useVideoPauseWorkaround; 573 } 574 575 /** 576 * Called by {@code ImsPhoneConnection} when there is a change to the video state of the call. 577 * Informs the video pause tracker that the video is no longer paused. This ensures that 578 * subsequent pause requests are not filtered out. 579 * 580 * @param newVideoState The new video state. 581 */ onVideoStateChanged(int newVideoState)582 public void onVideoStateChanged(int newVideoState) { 583 if (VideoProfile.isPaused(mCurrentVideoState) && !VideoProfile.isPaused(newVideoState)) { 584 // New video state is un-paused, so clear any pending pause requests. 585 Log.i(LOG_TAG, String.format("onVideoStateChanged: currentVideoState=%s," 586 + " newVideoState=%s, clearing pending pause requests.", 587 VideoProfile.videoStateToString(mCurrentVideoState), 588 VideoProfile.videoStateToString(newVideoState))); 589 mVideoPauseTracker.clearPauseRequests(); 590 } else { 591 Log.d(LOG_TAG, 592 String.format("onVideoStateChanged: currentVideoState=%s, newVideoState=%s", 593 VideoProfile.videoStateToString(mCurrentVideoState), 594 VideoProfile.videoStateToString(newVideoState))); 595 } 596 mCurrentVideoState = newVideoState; 597 } 598 599 /** 600 * Sets whether video is enabled locally or not. 601 * Used to reject incoming video requests when video is disabled locally due to data being 602 * disabled on a call where video calls are metered. 603 * @param isVideoEnabled {@code true} if video is locally enabled, {@code false} otherwise. 604 */ setIsVideoEnabled(boolean isVideoEnabled)605 public void setIsVideoEnabled(boolean isVideoEnabled) { 606 mIsVideoEnabled = isVideoEnabled; 607 } 608 609 /** 610 * Tears down the ImsVideoCallProviderWrapper. 611 */ tearDown()612 public void tearDown() { 613 if (mDeathRecipient != null) { 614 try { 615 mVideoCallProvider.asBinder().unlinkToDeath(mDeathRecipient, 0); 616 } catch (NoSuchElementException nse) { 617 // Already unlinked in binderDied above. 618 } 619 } 620 } 621 } 622