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