• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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