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