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