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