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.media.tv;
18
19import android.annotation.Nullable;
20import android.content.Context;
21import android.graphics.Rect;
22import android.media.PlaybackParams;
23import android.net.Uri;
24import android.os.Bundle;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.util.Log;
29import android.view.InputChannel;
30import android.view.InputEvent;
31import android.view.InputEventReceiver;
32import android.view.Surface;
33
34import com.android.internal.os.HandlerCaller;
35import com.android.internal.os.SomeArgs;
36
37/**
38 * Implements the internal ITvInputSession interface to convert incoming calls on to it back to
39 * calls on the public TvInputSession interface, scheduling them on the main thread of the process.
40 *
41 * @hide
42 */
43public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback {
44    private static final String TAG = "TvInputSessionWrapper";
45
46    private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 50;
47    private static final int EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS = 2000;
48    private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000;
49
50    private static final int DO_RELEASE = 1;
51    private static final int DO_SET_MAIN = 2;
52    private static final int DO_SET_SURFACE = 3;
53    private static final int DO_DISPATCH_SURFACE_CHANGED = 4;
54    private static final int DO_SET_STREAM_VOLUME = 5;
55    private static final int DO_TUNE = 6;
56    private static final int DO_SET_CAPTION_ENABLED = 7;
57    private static final int DO_SELECT_TRACK = 8;
58    private static final int DO_APP_PRIVATE_COMMAND = 9;
59    private static final int DO_CREATE_OVERLAY_VIEW = 10;
60    private static final int DO_RELAYOUT_OVERLAY_VIEW = 11;
61    private static final int DO_REMOVE_OVERLAY_VIEW = 12;
62    private static final int DO_UNBLOCK_CONTENT = 13;
63    private static final int DO_TIME_SHIFT_PLAY = 14;
64    private static final int DO_TIME_SHIFT_PAUSE = 15;
65    private static final int DO_TIME_SHIFT_RESUME = 16;
66    private static final int DO_TIME_SHIFT_SEEK_TO = 17;
67    private static final int DO_TIME_SHIFT_SET_PLAYBACK_PARAMS = 18;
68    private static final int DO_TIME_SHIFT_ENABLE_POSITION_TRACKING = 19;
69    private static final int DO_START_RECORDING = 20;
70    private static final int DO_STOP_RECORDING = 21;
71
72    private final boolean mIsRecordingSession;
73    private final HandlerCaller mCaller;
74
75    private TvInputService.Session mTvInputSessionImpl;
76    private TvInputService.RecordingSession mTvInputRecordingSessionImpl;
77
78    private InputChannel mChannel;
79    private TvInputEventReceiver mReceiver;
80
81    public ITvInputSessionWrapper(Context context, TvInputService.Session sessionImpl,
82            InputChannel channel) {
83        mIsRecordingSession = false;
84        mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
85        mTvInputSessionImpl = sessionImpl;
86        mChannel = channel;
87        if (channel != null) {
88            mReceiver = new TvInputEventReceiver(channel, context.getMainLooper());
89        }
90    }
91
92    // For the recording session
93    public ITvInputSessionWrapper(Context context,
94            TvInputService.RecordingSession recordingSessionImpl) {
95        mIsRecordingSession = true;
96        mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
97        mTvInputRecordingSessionImpl = recordingSessionImpl;
98    }
99
100    @Override
101    public void executeMessage(Message msg) {
102        if ((mIsRecordingSession && mTvInputRecordingSessionImpl == null)
103                || (!mIsRecordingSession && mTvInputSessionImpl == null)) {
104            return;
105        }
106
107        long startTime = System.nanoTime();
108        switch (msg.what) {
109            case DO_RELEASE: {
110                if (mIsRecordingSession) {
111                    mTvInputRecordingSessionImpl.release();
112                    mTvInputRecordingSessionImpl = null;
113                } else {
114                    mTvInputSessionImpl.release();
115                    mTvInputSessionImpl = null;
116                    if (mReceiver != null) {
117                        mReceiver.dispose();
118                        mReceiver = null;
119                    }
120                    if (mChannel != null) {
121                        mChannel.dispose();
122                        mChannel = null;
123                    }
124                }
125                break;
126            }
127            case DO_SET_MAIN: {
128                mTvInputSessionImpl.setMain((Boolean) msg.obj);
129                break;
130            }
131            case DO_SET_SURFACE: {
132                mTvInputSessionImpl.setSurface((Surface) msg.obj);
133                break;
134            }
135            case DO_DISPATCH_SURFACE_CHANGED: {
136                SomeArgs args = (SomeArgs) msg.obj;
137                mTvInputSessionImpl.dispatchSurfaceChanged(args.argi1, args.argi2, args.argi3);
138                args.recycle();
139                break;
140            }
141            case DO_SET_STREAM_VOLUME: {
142                mTvInputSessionImpl.setStreamVolume((Float) msg.obj);
143                break;
144            }
145            case DO_TUNE: {
146                SomeArgs args = (SomeArgs) msg.obj;
147                if (mIsRecordingSession) {
148                    mTvInputRecordingSessionImpl.tune((Uri) args.arg1, (Bundle) args.arg2);
149                } else {
150                    mTvInputSessionImpl.tune((Uri) args.arg1, (Bundle) args.arg2);
151                }
152                args.recycle();
153                break;
154            }
155            case DO_SET_CAPTION_ENABLED: {
156                mTvInputSessionImpl.setCaptionEnabled((Boolean) msg.obj);
157                break;
158            }
159            case DO_SELECT_TRACK: {
160                SomeArgs args = (SomeArgs) msg.obj;
161                mTvInputSessionImpl.selectTrack((Integer) args.arg1, (String) args.arg2);
162                args.recycle();
163                break;
164            }
165            case DO_APP_PRIVATE_COMMAND: {
166                SomeArgs args = (SomeArgs) msg.obj;
167                if (mIsRecordingSession) {
168                    mTvInputRecordingSessionImpl.appPrivateCommand(
169                            (String) args.arg1, (Bundle) args.arg2);
170                } else {
171                    mTvInputSessionImpl.appPrivateCommand((String) args.arg1, (Bundle) args.arg2);
172                }
173                args.recycle();
174                break;
175            }
176            case DO_CREATE_OVERLAY_VIEW: {
177                SomeArgs args = (SomeArgs) msg.obj;
178                mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
179                args.recycle();
180                break;
181            }
182            case DO_RELAYOUT_OVERLAY_VIEW: {
183                mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj);
184                break;
185            }
186            case DO_REMOVE_OVERLAY_VIEW: {
187                mTvInputSessionImpl.removeOverlayView(true);
188                break;
189            }
190            case DO_UNBLOCK_CONTENT: {
191                mTvInputSessionImpl.unblockContent((String) msg.obj);
192                break;
193            }
194            case DO_TIME_SHIFT_PLAY: {
195                mTvInputSessionImpl.timeShiftPlay((Uri) msg.obj);
196                break;
197            }
198            case DO_TIME_SHIFT_PAUSE: {
199                mTvInputSessionImpl.timeShiftPause();
200                break;
201            }
202            case DO_TIME_SHIFT_RESUME: {
203                mTvInputSessionImpl.timeShiftResume();
204                break;
205            }
206            case DO_TIME_SHIFT_SEEK_TO: {
207                mTvInputSessionImpl.timeShiftSeekTo((Long) msg.obj);
208                break;
209            }
210            case DO_TIME_SHIFT_SET_PLAYBACK_PARAMS: {
211                mTvInputSessionImpl.timeShiftSetPlaybackParams((PlaybackParams) msg.obj);
212                break;
213            }
214            case DO_TIME_SHIFT_ENABLE_POSITION_TRACKING: {
215                mTvInputSessionImpl.timeShiftEnablePositionTracking((Boolean) msg.obj);
216                break;
217            }
218            case DO_START_RECORDING: {
219                mTvInputRecordingSessionImpl.startRecording((Uri) msg.obj);
220                break;
221            }
222            case DO_STOP_RECORDING: {
223                mTvInputRecordingSessionImpl.stopRecording();
224                break;
225            }
226            default: {
227                Log.w(TAG, "Unhandled message code: " + msg.what);
228                break;
229            }
230        }
231        long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
232        if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
233            Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
234                    + durationMs + "ms)");
235            if (msg.what == DO_TUNE && durationMs > EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS) {
236                throw new RuntimeException("Too much time to handle tune request. (" + durationMs
237                        + "ms > " + EXECUTE_MESSAGE_TUNE_TIMEOUT_MILLIS + "ms) "
238                        + "Consider handling the tune request in a separate thread.");
239            }
240            if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
241                throw new RuntimeException("Too much time to handle a request. (type=" + msg.what +
242                        ", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
243            }
244        }
245    }
246
247    @Override
248    public void release() {
249        if (!mIsRecordingSession) {
250            mTvInputSessionImpl.scheduleOverlayViewCleanup();
251        }
252        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
253    }
254
255    @Override
256    public void setMain(boolean isMain) {
257        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_MAIN, isMain));
258    }
259
260    @Override
261    public void setSurface(Surface surface) {
262        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
263    }
264
265    @Override
266    public void dispatchSurfaceChanged(int format, int width, int height) {
267        mCaller.executeOrSendMessage(mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED,
268                format, width, height, 0));
269    }
270
271    @Override
272    public final void setVolume(float volume) {
273        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_STREAM_VOLUME, volume));
274    }
275
276    @Override
277    public void tune(Uri channelUri, Bundle params) {
278        // Clear the pending tune requests.
279        mCaller.removeMessages(DO_TUNE);
280        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_TUNE, channelUri, params));
281    }
282
283    @Override
284    public void setCaptionEnabled(boolean enabled) {
285        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_CAPTION_ENABLED, enabled));
286    }
287
288    @Override
289    public void selectTrack(int type, String trackId) {
290        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SELECT_TRACK, type, trackId));
291    }
292
293    @Override
294    public void appPrivateCommand(String action, Bundle data) {
295        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action,
296                data));
297    }
298
299    @Override
300    public void createOverlayView(IBinder windowToken, Rect frame) {
301        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
302                frame));
303    }
304
305    @Override
306    public void relayoutOverlayView(Rect frame) {
307        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
308    }
309
310    @Override
311    public void removeOverlayView() {
312        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
313    }
314
315    @Override
316    public void unblockContent(String unblockedRating) {
317        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
318                DO_UNBLOCK_CONTENT, unblockedRating));
319    }
320
321    @Override
322    public void timeShiftPlay(Uri recordedProgramUri) {
323        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
324                DO_TIME_SHIFT_PLAY, recordedProgramUri));
325    }
326
327    @Override
328    public void timeShiftPause() {
329        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_PAUSE));
330    }
331
332    @Override
333    public void timeShiftResume() {
334        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_RESUME));
335    }
336
337    @Override
338    public void timeShiftSeekTo(long timeMs) {
339        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SEEK_TO, timeMs));
340    }
341
342    @Override
343    public void timeShiftSetPlaybackParams(PlaybackParams params) {
344        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SET_PLAYBACK_PARAMS,
345                params));
346    }
347
348    @Override
349    public void timeShiftEnablePositionTracking(boolean enable) {
350        mCaller.executeOrSendMessage(mCaller.obtainMessageO(
351                DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, enable));
352    }
353
354    @Override
355    public void startRecording(@Nullable Uri programUri) {
356        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_RECORDING, programUri));
357    }
358
359    @Override
360    public void stopRecording() {
361        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_RECORDING));
362    }
363
364    private final class TvInputEventReceiver extends InputEventReceiver {
365        public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
366            super(inputChannel, looper);
367        }
368
369        @Override
370        public void onInputEvent(InputEvent event) {
371            if (mTvInputSessionImpl == null) {
372                // The session has been finished.
373                finishInputEvent(event, false);
374                return;
375            }
376
377            int handled = mTvInputSessionImpl.dispatchInputEvent(event, this);
378            if (handled != TvInputManager.Session.DISPATCH_IN_PROGRESS) {
379                finishInputEvent(event, handled == TvInputManager.Session.DISPATCH_HANDLED);
380            }
381        }
382    }
383}
384