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 android.telecom;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.net.Uri;
21 import android.os.Build;
22 import android.os.Handler;
23 import android.os.IBinder;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.os.RemoteException;
27 import android.telecom.InCallService.VideoCall;
28 import android.view.Surface;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.os.SomeArgs;
32 import com.android.internal.telecom.IVideoCallback;
33 import com.android.internal.telecom.IVideoProvider;
34 
35 import java.util.NoSuchElementException;
36 
37 /**
38  * Implementation of a Video Call, which allows InCallUi to communicate commands to the underlying
39  * {@link Connection.VideoProvider}, and direct callbacks from the
40  * {@link Connection.VideoProvider} to the appropriate {@link VideoCall.Listener}.
41  *
42  * {@hide}
43  */
44 public class VideoCallImpl extends VideoCall {
45 
46     private final IVideoProvider mVideoProvider;
47     private final VideoCallListenerBinder mBinder;
48     private VideoCall.Callback mCallback;
49     private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN;
50     private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
51     private final String mCallingPackageName;
52 
53     private int mTargetSdkVersion;
54 
55     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
56         @Override
57         public void binderDied() {
58             try {
59                 mVideoProvider.asBinder().unlinkToDeath(this, 0);
60             } catch (NoSuchElementException nse) {
61                 // Already unlinked in destroy below.
62             }
63         }
64     };
65 
66     /**
67      * IVideoCallback stub implementation.
68      */
69     private final class VideoCallListenerBinder extends IVideoCallback.Stub {
70         @Override
receiveSessionModifyRequest(VideoProfile videoProfile)71         public void receiveSessionModifyRequest(VideoProfile videoProfile) {
72             if (mHandler == null) {
73                 return;
74             }
75             mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_REQUEST,
76                     videoProfile).sendToTarget();
77 
78         }
79 
80         @Override
receiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)81         public void receiveSessionModifyResponse(int status, VideoProfile requestProfile,
82                 VideoProfile responseProfile) {
83             if (mHandler == null) {
84                 return;
85             }
86             SomeArgs args = SomeArgs.obtain();
87             args.arg1 = status;
88             args.arg2 = requestProfile;
89             args.arg3 = responseProfile;
90             mHandler.obtainMessage(MessageHandler.MSG_RECEIVE_SESSION_MODIFY_RESPONSE, args)
91                     .sendToTarget();
92         }
93 
94         @Override
handleCallSessionEvent(int event)95         public void handleCallSessionEvent(int event) {
96             if (mHandler == null) {
97                 return;
98             }
99             mHandler.obtainMessage(MessageHandler.MSG_HANDLE_CALL_SESSION_EVENT, event)
100                     .sendToTarget();
101         }
102 
103         @Override
changePeerDimensions(int width, int height)104         public void changePeerDimensions(int width, int height) {
105             if (mHandler == null) {
106                 return;
107             }
108             SomeArgs args = SomeArgs.obtain();
109             args.arg1 = width;
110             args.arg2 = height;
111             mHandler.obtainMessage(MessageHandler.MSG_CHANGE_PEER_DIMENSIONS, args).sendToTarget();
112         }
113 
114         @Override
changeVideoQuality(int videoQuality)115         public void changeVideoQuality(int videoQuality) {
116             if (mHandler == null) {
117                 return;
118             }
119             mHandler.obtainMessage(MessageHandler.MSG_CHANGE_VIDEO_QUALITY, videoQuality, 0)
120                     .sendToTarget();
121         }
122 
123         @Override
changeCallDataUsage(long dataUsage)124         public void changeCallDataUsage(long dataUsage) {
125             if (mHandler == null) {
126                 return;
127             }
128             mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CALL_DATA_USAGE, dataUsage)
129                     .sendToTarget();
130         }
131 
132         @Override
changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities)133         public void changeCameraCapabilities(VideoProfile.CameraCapabilities cameraCapabilities) {
134             if (mHandler == null) {
135                 return;
136             }
137             mHandler.obtainMessage(MessageHandler.MSG_CHANGE_CAMERA_CAPABILITIES,
138                     cameraCapabilities).sendToTarget();
139         }
140     }
141 
142     /** Default handler used to consolidate binder method calls onto a single thread. */
143     private final class MessageHandler extends Handler {
144         private static final int MSG_RECEIVE_SESSION_MODIFY_REQUEST = 1;
145         private static final int MSG_RECEIVE_SESSION_MODIFY_RESPONSE = 2;
146         private static final int MSG_HANDLE_CALL_SESSION_EVENT = 3;
147         private static final int MSG_CHANGE_PEER_DIMENSIONS = 4;
148         private static final int MSG_CHANGE_CALL_DATA_USAGE = 5;
149         private static final int MSG_CHANGE_CAMERA_CAPABILITIES = 6;
150         private static final int MSG_CHANGE_VIDEO_QUALITY = 7;
151 
MessageHandler(Looper looper)152         public MessageHandler(Looper looper) {
153             super(looper);
154         }
155 
156         @Override
handleMessage(Message msg)157         public void handleMessage(Message msg) {
158             if (mCallback == null) {
159                 return;
160             }
161 
162             SomeArgs args;
163             switch (msg.what) {
164                 case MSG_RECEIVE_SESSION_MODIFY_REQUEST:
165                     mCallback.onSessionModifyRequestReceived((VideoProfile) msg.obj);
166                     break;
167                 case MSG_RECEIVE_SESSION_MODIFY_RESPONSE:
168                     args = (SomeArgs) msg.obj;
169                     try {
170                         int status = (int) args.arg1;
171                         VideoProfile requestProfile = (VideoProfile) args.arg2;
172                         VideoProfile responseProfile = (VideoProfile) args.arg3;
173 
174                         mCallback.onSessionModifyResponseReceived(
175                                 status, requestProfile, responseProfile);
176                     } finally {
177                         args.recycle();
178                     }
179                     break;
180                 case MSG_HANDLE_CALL_SESSION_EVENT:
181                     mCallback.onCallSessionEvent((int) msg.obj);
182                     break;
183                 case MSG_CHANGE_PEER_DIMENSIONS:
184                     args = (SomeArgs) msg.obj;
185                     try {
186                         int width = (int) args.arg1;
187                         int height = (int) args.arg2;
188                         mCallback.onPeerDimensionsChanged(width, height);
189                     } finally {
190                         args.recycle();
191                     }
192                     break;
193                 case MSG_CHANGE_CALL_DATA_USAGE:
194                     mCallback.onCallDataUsageChanged((long) msg.obj);
195                     break;
196                 case MSG_CHANGE_CAMERA_CAPABILITIES:
197                     mCallback.onCameraCapabilitiesChanged(
198                             (VideoProfile.CameraCapabilities) msg.obj);
199                     break;
200                 case MSG_CHANGE_VIDEO_QUALITY:
201                     mVideoQuality = msg.arg1;
202                     mCallback.onVideoQualityChanged(msg.arg1);
203                     break;
204                 default:
205                     break;
206             }
207         }
208     };
209 
210     private Handler mHandler;
211 
VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion)212     VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion)
213             throws RemoteException {
214         mVideoProvider = videoProvider;
215         mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
216 
217         mBinder = new VideoCallListenerBinder();
218         mVideoProvider.addVideoCallback(mBinder);
219         mCallingPackageName = callingPackageName;
220         setTargetSdkVersion(targetSdkVersion);
221     }
222 
223     @VisibleForTesting
setTargetSdkVersion(int sdkVersion)224     public void setTargetSdkVersion(int sdkVersion) {
225         mTargetSdkVersion = sdkVersion;
226     }
227 
228     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196)
destroy()229     public void destroy() {
230         unregisterCallback(mCallback);
231         try {
232             mVideoProvider.asBinder().unlinkToDeath(mDeathRecipient, 0);
233         } catch (NoSuchElementException nse) {
234             // Already unlinked in binderDied above.
235         }
236     }
237 
238     /** {@inheritDoc} */
registerCallback(VideoCall.Callback callback)239     public void registerCallback(VideoCall.Callback callback) {
240         registerCallback(callback, null);
241     }
242 
243     /** {@inheritDoc} */
registerCallback(VideoCall.Callback callback, Handler handler)244     public void registerCallback(VideoCall.Callback callback, Handler handler) {
245         mCallback = callback;
246         if (handler == null) {
247             mHandler = new MessageHandler(Looper.getMainLooper());
248         } else {
249             mHandler = new MessageHandler(handler.getLooper());
250         }
251     }
252 
253     /** {@inheritDoc} */
unregisterCallback(VideoCall.Callback callback)254     public void unregisterCallback(VideoCall.Callback callback) {
255         if (callback != mCallback) {
256             return;
257         }
258 
259         mCallback = null;
260         try {
261             mVideoProvider.removeVideoCallback(mBinder);
262         } catch (RemoteException e) {
263         }
264     }
265 
266     /** {@inheritDoc} */
setCamera(String cameraId)267     public void setCamera(String cameraId) {
268         try {
269             Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName);
270             mVideoProvider.setCamera(cameraId, mCallingPackageName, mTargetSdkVersion);
271         } catch (RemoteException e) {
272         }
273     }
274 
275     /** {@inheritDoc} */
setPreviewSurface(Surface surface)276     public void setPreviewSurface(Surface surface) {
277         try {
278             mVideoProvider.setPreviewSurface(surface);
279         } catch (RemoteException e) {
280         }
281     }
282 
283     /** {@inheritDoc} */
setDisplaySurface(Surface surface)284     public void setDisplaySurface(Surface surface) {
285         try {
286             mVideoProvider.setDisplaySurface(surface);
287         } catch (RemoteException e) {
288         }
289     }
290 
291     /** {@inheritDoc} */
setDeviceOrientation(int rotation)292     public void setDeviceOrientation(int rotation) {
293         try {
294             mVideoProvider.setDeviceOrientation(rotation);
295         } catch (RemoteException e) {
296         }
297     }
298 
299     /** {@inheritDoc} */
setZoom(float value)300     public void setZoom(float value) {
301         try {
302             mVideoProvider.setZoom(value);
303         } catch (RemoteException e) {
304         }
305     }
306 
307     /**
308      * Sends a session modification request to the video provider.
309      * <p>
310      * The {@link InCallService} will create the {@code requestProfile} based on the current
311      * video state (i.e. {@link Call.Details#getVideoState()}).  It is, however, possible that the
312      * video state maintained by the {@link InCallService} could get out of sync with what is known
313      * by the {@link android.telecom.Connection.VideoProvider}.  To remove ambiguity, the
314      * {@link VideoCallImpl} passes along the pre-modify video profile to the {@code VideoProvider}
315      * to ensure it has full context of the requested change.
316      *
317      * @param requestProfile The requested video profile.
318      */
sendSessionModifyRequest(VideoProfile requestProfile)319     public void sendSessionModifyRequest(VideoProfile requestProfile) {
320         try {
321             VideoProfile originalProfile = new VideoProfile(mVideoState, mVideoQuality);
322 
323             mVideoProvider.sendSessionModifyRequest(originalProfile, requestProfile);
324         } catch (RemoteException e) {
325         }
326     }
327 
328     /** {@inheritDoc} */
sendSessionModifyResponse(VideoProfile responseProfile)329     public void sendSessionModifyResponse(VideoProfile responseProfile) {
330         try {
331             mVideoProvider.sendSessionModifyResponse(responseProfile);
332         } catch (RemoteException e) {
333         }
334     }
335 
336     /** {@inheritDoc} */
requestCameraCapabilities()337     public void requestCameraCapabilities() {
338         try {
339             mVideoProvider.requestCameraCapabilities();
340         } catch (RemoteException e) {
341         }
342     }
343 
344     /** {@inheritDoc} */
requestCallDataUsage()345     public void requestCallDataUsage() {
346         try {
347             mVideoProvider.requestCallDataUsage();
348         } catch (RemoteException e) {
349         }
350     }
351 
352     /** {@inheritDoc} */
setPauseImage(Uri uri)353     public void setPauseImage(Uri uri) {
354         try {
355             mVideoProvider.setPauseImage(uri);
356         } catch (RemoteException e) {
357         }
358     }
359 
360     /**
361      * Sets the video state for the current video call.
362      * @param videoState the new video state.
363      */
setVideoState(int videoState)364     public void setVideoState(int videoState) {
365         mVideoState = videoState;
366     }
367 
368     /**
369      * Get the video provider binder.
370      * @return the video provider binder.
371      */
getVideoProvider()372     public IVideoProvider getVideoProvider() {
373         return mVideoProvider;
374     }
375 }
376