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.media.tv;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.graphics.Rect;
22 import android.media.PlaybackParams;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.util.Log;
29 import android.view.InputChannel;
30 import android.view.InputEvent;
31 import android.view.InputEventReceiver;
32 import android.view.Surface;
33 
34 import com.android.internal.os.HandlerCaller;
35 import 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  */
43 public 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 
ITvInputSessionWrapper(Context context, TvInputService.Session sessionImpl, InputChannel channel)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
ITvInputSessionWrapper(Context context, TvInputService.RecordingSession recordingSessionImpl)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
executeMessage(Message msg)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
release()248     public void release() {
249         if (!mIsRecordingSession) {
250             mTvInputSessionImpl.scheduleOverlayViewCleanup();
251         }
252         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
253     }
254 
255     @Override
setMain(boolean isMain)256     public void setMain(boolean isMain) {
257         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_MAIN, isMain));
258     }
259 
260     @Override
setSurface(Surface surface)261     public void setSurface(Surface surface) {
262         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
263     }
264 
265     @Override
dispatchSurfaceChanged(int format, int width, int height)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
setVolume(float volume)272     public final void setVolume(float volume) {
273         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_STREAM_VOLUME, volume));
274     }
275 
276     @Override
tune(Uri channelUri, Bundle params)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
setCaptionEnabled(boolean enabled)284     public void setCaptionEnabled(boolean enabled) {
285         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_CAPTION_ENABLED, enabled));
286     }
287 
288     @Override
selectTrack(int type, String trackId)289     public void selectTrack(int type, String trackId) {
290         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SELECT_TRACK, type, trackId));
291     }
292 
293     @Override
appPrivateCommand(String action, Bundle data)294     public void appPrivateCommand(String action, Bundle data) {
295         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action,
296                 data));
297     }
298 
299     @Override
createOverlayView(IBinder windowToken, Rect frame)300     public void createOverlayView(IBinder windowToken, Rect frame) {
301         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
302                 frame));
303     }
304 
305     @Override
relayoutOverlayView(Rect frame)306     public void relayoutOverlayView(Rect frame) {
307         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
308     }
309 
310     @Override
removeOverlayView()311     public void removeOverlayView() {
312         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
313     }
314 
315     @Override
unblockContent(String unblockedRating)316     public void unblockContent(String unblockedRating) {
317         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
318                 DO_UNBLOCK_CONTENT, unblockedRating));
319     }
320 
321     @Override
timeShiftPlay(Uri recordedProgramUri)322     public void timeShiftPlay(Uri recordedProgramUri) {
323         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
324                 DO_TIME_SHIFT_PLAY, recordedProgramUri));
325     }
326 
327     @Override
timeShiftPause()328     public void timeShiftPause() {
329         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_PAUSE));
330     }
331 
332     @Override
timeShiftResume()333     public void timeShiftResume() {
334         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_RESUME));
335     }
336 
337     @Override
timeShiftSeekTo(long timeMs)338     public void timeShiftSeekTo(long timeMs) {
339         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SEEK_TO, timeMs));
340     }
341 
342     @Override
timeShiftSetPlaybackParams(PlaybackParams params)343     public void timeShiftSetPlaybackParams(PlaybackParams params) {
344         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SET_PLAYBACK_PARAMS,
345                 params));
346     }
347 
348     @Override
timeShiftEnablePositionTracking(boolean enable)349     public void timeShiftEnablePositionTracking(boolean enable) {
350         mCaller.executeOrSendMessage(mCaller.obtainMessageO(
351                 DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, enable));
352     }
353 
354     @Override
startRecording(@ullable Uri programUri)355     public void startRecording(@Nullable Uri programUri) {
356         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_RECORDING, programUri));
357     }
358 
359     @Override
stopRecording()360     public void stopRecording() {
361         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_RECORDING));
362     }
363 
364     private final class TvInputEventReceiver extends InputEventReceiver {
TvInputEventReceiver(InputChannel inputChannel, Looper looper)365         public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
366             super(inputChannel, looper);
367         }
368 
369         @Override
onInputEvent(InputEvent event)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