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