• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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.interactive;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.MainThread;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.Px;
24 import android.annotation.SdkConstant;
25 import android.annotation.StringDef;
26 import android.annotation.SuppressLint;
27 import android.app.ActivityManager;
28 import android.app.Service;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.graphics.PixelFormat;
32 import android.graphics.Rect;
33 import android.media.tv.AdRequest;
34 import android.media.tv.AdResponse;
35 import android.media.tv.BroadcastInfoRequest;
36 import android.media.tv.BroadcastInfoResponse;
37 import android.media.tv.TvContentRating;
38 import android.media.tv.TvInputInfo;
39 import android.media.tv.TvInputManager;
40 import android.media.tv.TvTrackInfo;
41 import android.media.tv.TvView;
42 import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
43 import android.net.Uri;
44 import android.os.AsyncTask;
45 import android.os.Bundle;
46 import android.os.Handler;
47 import android.os.IBinder;
48 import android.os.Looper;
49 import android.os.Message;
50 import android.os.Process;
51 import android.os.RemoteCallbackList;
52 import android.os.RemoteException;
53 import android.util.Log;
54 import android.view.Gravity;
55 import android.view.InputChannel;
56 import android.view.InputDevice;
57 import android.view.InputEvent;
58 import android.view.InputEventReceiver;
59 import android.view.KeyEvent;
60 import android.view.MotionEvent;
61 import android.view.Surface;
62 import android.view.View;
63 import android.view.WindowManager;
64 import android.widget.FrameLayout;
65 
66 import com.android.internal.os.SomeArgs;
67 
68 import java.lang.annotation.Retention;
69 import java.lang.annotation.RetentionPolicy;
70 import java.util.ArrayList;
71 import java.util.List;
72 
73 /**
74  * A TV interactive application service is a service that provides runtime environment and runs TV
75  * interactive applications.
76  */
77 public abstract class TvInteractiveAppService extends Service {
78     private static final boolean DEBUG = false;
79     private static final String TAG = "TvInteractiveAppService";
80 
81     private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
82 
83     /**
84      * This is the interface name that a service implementing a TV Interactive App service should
85      * say that it supports -- that is, this is the action it uses for its intent filter. To be
86      * supported, the service must also require the
87      * {@link android.Manifest.permission#BIND_TV_INTERACTIVE_APP} permission so that other
88      * applications cannot abuse it.
89      */
90     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
91     public static final String SERVICE_INTERFACE =
92             "android.media.tv.interactive.TvInteractiveAppService";
93 
94     /**
95      * Name under which a TvInteractiveAppService component publishes information about itself. This
96      * meta-data must reference an XML resource containing an
97      * <code>&lt;{@link android.R.styleable#TvInteractiveAppService tv-interactive-app}&gt;</code>
98      * tag.
99      */
100     public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
101 
102     /** @hide */
103     @Retention(RetentionPolicy.SOURCE)
104     @StringDef(prefix = "PLAYBACK_COMMAND_TYPE_", value = {
105             PLAYBACK_COMMAND_TYPE_TUNE,
106             PLAYBACK_COMMAND_TYPE_TUNE_NEXT,
107             PLAYBACK_COMMAND_TYPE_TUNE_PREV,
108             PLAYBACK_COMMAND_TYPE_STOP,
109             PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
110             PLAYBACK_COMMAND_TYPE_SELECT_TRACK
111     })
112     public @interface PlaybackCommandType {}
113 
114     /**
115      * Playback command type: tune to the given channel.
116      * @see #COMMAND_PARAMETER_KEY_CHANNEL_URI
117      */
118     public static final String PLAYBACK_COMMAND_TYPE_TUNE = "tune";
119     /**
120      * Playback command type: tune to the next channel.
121      */
122     public static final String PLAYBACK_COMMAND_TYPE_TUNE_NEXT = "tune_next";
123     /**
124      * Playback command type: tune to the previous channel.
125      */
126     public static final String PLAYBACK_COMMAND_TYPE_TUNE_PREV = "tune_previous";
127     /**
128      * Playback command type: stop the playback.
129      */
130     public static final String PLAYBACK_COMMAND_TYPE_STOP = "stop";
131     /**
132      * Playback command type: set the volume.
133      */
134     public static final String PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME =
135             "set_stream_volume";
136     /**
137      * Playback command type: select the given track.
138      */
139     public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
140     /**
141      * Playback command parameter: channel URI.
142      * <p>Type: android.net.Uri
143      *
144      * @see #PLAYBACK_COMMAND_TYPE_TUNE
145      */
146     public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
147     /**
148      * Playback command parameter: TV input ID.
149      * <p>Type: String
150      *
151      * @see TvInputInfo#getId()
152      */
153     public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
154     /**
155      * Playback command parameter: stream volume.
156      * <p>Type: float
157      *
158      * @see #PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME
159      */
160     public static final String COMMAND_PARAMETER_KEY_VOLUME = "command_volume";
161     /**
162      * Playback command parameter: track type.
163      * <p>Type: int
164      *
165      * @see #PLAYBACK_COMMAND_TYPE_SELECT_TRACK
166      * @see TvTrackInfo#getType()
167      */
168     public static final String COMMAND_PARAMETER_KEY_TRACK_TYPE = "command_track_type";
169     /**
170      * Playback command parameter: track ID.
171      * <p>Type: String
172      *
173      * @see #PLAYBACK_COMMAND_TYPE_SELECT_TRACK
174      * @see TvTrackInfo#getId()
175      */
176     public static final String COMMAND_PARAMETER_KEY_TRACK_ID = "command_track_id";
177     /**
178      * Command to quiet channel change. No channel banner or channel info is shown.
179      * <p>Refer to HbbTV Spec 2.0.4 chapter A.2.4.3.
180      */
181     public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY =
182             "command_change_channel_quietly";
183 
184     private final Handler mServiceHandler = new ServiceHandler();
185     private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
186             new RemoteCallbackList<>();
187 
188     @Override
189     @Nullable
onBind(@onNull Intent intent)190     public final IBinder onBind(@NonNull Intent intent) {
191         ITvInteractiveAppService.Stub tvIAppServiceBinder = new ITvInteractiveAppService.Stub() {
192             @Override
193             public void registerCallback(ITvInteractiveAppServiceCallback cb) {
194                 if (cb != null) {
195                     mCallbacks.register(cb);
196                 }
197             }
198 
199             @Override
200             public void unregisterCallback(ITvInteractiveAppServiceCallback cb) {
201                 if (cb != null) {
202                     mCallbacks.unregister(cb);
203                 }
204             }
205 
206             @Override
207             public void createSession(InputChannel channel, ITvInteractiveAppSessionCallback cb,
208                     String iAppServiceId, int type) {
209                 if (cb == null) {
210                     return;
211                 }
212                 SomeArgs args = SomeArgs.obtain();
213                 args.arg1 = channel;
214                 args.arg2 = cb;
215                 args.arg3 = iAppServiceId;
216                 args.arg4 = type;
217                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
218                         .sendToTarget();
219             }
220 
221             @Override
222             public void registerAppLinkInfo(AppLinkInfo appLinkInfo) {
223                 onRegisterAppLinkInfo(appLinkInfo);
224             }
225 
226             @Override
227             public void unregisterAppLinkInfo(AppLinkInfo appLinkInfo) {
228                 onUnregisterAppLinkInfo(appLinkInfo);
229             }
230 
231             @Override
232             public void sendAppLinkCommand(Bundle command) {
233                 onAppLinkCommand(command);
234             }
235         };
236         return tvIAppServiceBinder;
237     }
238 
239     /**
240      * Called when a request to register an Android application link info record is received.
241      */
onRegisterAppLinkInfo(@onNull AppLinkInfo appLinkInfo)242     public void onRegisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
243     }
244 
245     /**
246      * Called when a request to unregister an Android application link info record is received.
247      */
onUnregisterAppLinkInfo(@onNull AppLinkInfo appLinkInfo)248     public void onUnregisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
249     }
250 
251     /**
252      * Called when app link command is received.
253      *
254      * @see android.media.tv.interactive.TvInteractiveAppManager#sendAppLinkCommand(String, Bundle)
255      */
onAppLinkCommand(@onNull Bundle command)256     public void onAppLinkCommand(@NonNull Bundle command) {
257     }
258 
259 
260     /**
261      * Returns a concrete implementation of {@link Session}.
262      *
263      * <p>May return {@code null} if this TV Interactive App service fails to create a session for
264      * some reason.
265      *
266      * @param iAppServiceId The ID of the TV Interactive App associated with the session.
267      * @param type The type of the TV Interactive App associated with the session.
268      */
269     @Nullable
onCreateSession( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type)270     public abstract Session onCreateSession(
271             @NonNull String iAppServiceId,
272             @TvInteractiveAppServiceInfo.InteractiveAppType int type);
273 
274     /**
275      * Notifies the system when the state of the interactive app RTE has been changed.
276      *
277      * @param type the interactive app type
278      * @param state the current state of the service of the given type
279      * @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
280      *              used when the state is not
281      *              {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}.
282      */
notifyStateChanged( @vInteractiveAppServiceInfo.InteractiveAppType int type, @TvInteractiveAppManager.ServiceState int state, @TvInteractiveAppManager.ErrorCode int error)283     public final void notifyStateChanged(
284             @TvInteractiveAppServiceInfo.InteractiveAppType int type,
285             @TvInteractiveAppManager.ServiceState int state,
286             @TvInteractiveAppManager.ErrorCode int error) {
287         SomeArgs args = SomeArgs.obtain();
288         args.arg1 = type;
289         args.arg2 = state;
290         args.arg3 = error;
291         mServiceHandler
292                 .obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED, args).sendToTarget();
293     }
294 
295     /**
296      * Base class for derived classes to implement to provide a TV interactive app session.
297      *
298      * <p>A session is associated with a {@link TvInteractiveAppView} instance and handles
299      * corresponding communications. It also handles the communications with
300      * {@link android.media.tv.TvInputService.Session} if connected.
301      *
302      * @see TvInteractiveAppView#setTvView(TvView)
303      */
304     public abstract static class Session implements KeyEvent.Callback {
305         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
306 
307         private final Object mLock = new Object();
308         // @GuardedBy("mLock")
309         private ITvInteractiveAppSessionCallback mSessionCallback;
310         // @GuardedBy("mLock")
311         private final List<Runnable> mPendingActions = new ArrayList<>();
312 
313         private final Context mContext;
314         final Handler mHandler;
315         private final WindowManager mWindowManager;
316         private WindowManager.LayoutParams mWindowParams;
317         private Surface mSurface;
318         private FrameLayout mMediaViewContainer;
319         private View mMediaView;
320         private MediaViewCleanUpTask mMediaViewCleanUpTask;
321         private boolean mMediaViewEnabled;
322         private IBinder mWindowToken;
323         private Rect mMediaFrame;
324 
325         /**
326          * Creates a new Session.
327          *
328          * @param context The context of the application
329          */
Session(@onNull Context context)330         public Session(@NonNull Context context) {
331             mContext = context;
332             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
333             mHandler = new Handler(context.getMainLooper());
334         }
335 
336         /**
337          * Enables or disables the media view.
338          *
339          * <p>By default, the media view is disabled. Must be called explicitly after the
340          * session is created to enable the media view.
341          *
342          * <p>The TV Interactive App service can disable its media view when needed.
343          *
344          * @param enable {@code true} if you want to enable the media view. {@code false}
345          *            otherwise.
346          */
347         @CallSuper
setMediaViewEnabled(final boolean enable)348         public void setMediaViewEnabled(final boolean enable) {
349             mHandler.post(new Runnable() {
350                 @Override
351                 public void run() {
352                     if (enable == mMediaViewEnabled) {
353                         return;
354                     }
355                     mMediaViewEnabled = enable;
356                     if (enable) {
357                         if (mWindowToken != null) {
358                             createMediaView(mWindowToken, mMediaFrame);
359                         }
360                     } else {
361                         removeMediaView(false);
362                     }
363                 }
364             });
365         }
366 
367         /**
368          * Returns {@code true} if media view is enabled, {@code false} otherwise.
369          *
370          * @see #setMediaViewEnabled(boolean)
371          */
isMediaViewEnabled()372         public boolean isMediaViewEnabled() {
373             return mMediaViewEnabled;
374         }
375 
376         /**
377          * Starts TvInteractiveAppService session.
378          */
onStartInteractiveApp()379         public void onStartInteractiveApp() {
380         }
381 
382         /**
383          * Stops TvInteractiveAppService session.
384          */
onStopInteractiveApp()385         public void onStopInteractiveApp() {
386         }
387 
388         /**
389          * Resets TvInteractiveAppService session.
390          */
onResetInteractiveApp()391         public void onResetInteractiveApp() {
392         }
393 
394         /**
395          * Creates broadcast-independent(BI) interactive application.
396          *
397          * <p>The implementation should call {@link #notifyBiInteractiveAppCreated(Uri, String)},
398          * no matter if it's created successfully or not.
399          *
400          * @see #notifyBiInteractiveAppCreated(Uri, String)
401          * @see #onDestroyBiInteractiveAppRequest(String)
402          */
onCreateBiInteractiveAppRequest( @onNull Uri biIAppUri, @Nullable Bundle params)403         public void onCreateBiInteractiveAppRequest(
404                 @NonNull Uri biIAppUri, @Nullable Bundle params) {
405         }
406 
407 
408         /**
409          * Destroys broadcast-independent(BI) interactive application.
410          *
411          * @param biIAppId the BI interactive app ID from
412          *                 {@link #onCreateBiInteractiveAppRequest(Uri, Bundle)}
413          *
414          * @see #onCreateBiInteractiveAppRequest(Uri, Bundle)
415          */
onDestroyBiInteractiveAppRequest(@onNull String biIAppId)416         public void onDestroyBiInteractiveAppRequest(@NonNull String biIAppId) {
417         }
418 
419         /**
420          * To toggle Digital Teletext Application if there is one in AIT app list.
421          * @param enable {@code true} to enable teletext app; {@code false} otherwise.
422          */
onSetTeletextAppEnabled(boolean enable)423         public void onSetTeletextAppEnabled(boolean enable) {
424         }
425 
426         /**
427          * Receives current channel URI.
428          */
onCurrentChannelUri(@ullable Uri channelUri)429         public void onCurrentChannelUri(@Nullable Uri channelUri) {
430         }
431 
432         /**
433          * Receives logical channel number (LCN) of current channel.
434          */
onCurrentChannelLcn(int lcn)435         public void onCurrentChannelLcn(int lcn) {
436         }
437 
438         /**
439          * Receives current stream volume.
440          *
441          * @param volume a volume value between {@code 0.0f} and {@code 1.0f}, inclusive.
442          */
onStreamVolume(float volume)443         public void onStreamVolume(float volume) {
444         }
445 
446         /**
447          * Receives track list.
448          */
onTrackInfoList(@onNull List<TvTrackInfo> tracks)449         public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
450         }
451 
452         /**
453          * Receives current TV input ID.
454          */
onCurrentTvInputId(@ullable String inputId)455         public void onCurrentTvInputId(@Nullable String inputId) {
456         }
457 
458         /**
459          * Receives signing result.
460          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
461          *        {@link Session#requestSigning(String, String, String, byte[])}
462          * @param result the signed result.
463          *
464          * @see #requestSigning(String, String, String, byte[])
465          */
onSigningResult(@onNull String signingId, @NonNull byte[] result)466         public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
467         }
468 
469         /**
470          * Called when the application sends information of an error.
471          *
472          * @param errMsg the message of the error.
473          * @param params additional parameters of the error. For example, the signingId of {@link
474          *     TvInteractiveAppCallback#onRequestSigning(String, String, String, String, byte[])}
475          *     can be included to identify the related signing request, and the method name
476          *     "onRequestSigning" can also be added to the params.
477          *
478          * @see TvInteractiveAppView#ERROR_KEY_METHOD_NAME
479          */
onError(@onNull String errMsg, @NonNull Bundle params)480         public void onError(@NonNull String errMsg, @NonNull Bundle params) {
481         }
482 
483         /**
484          * Called when the application sets the surface.
485          *
486          * <p>The TV Interactive App service should render interactive app UI onto the given
487          * surface. When called with {@code null}, the Interactive App service should immediately
488          * free any references to the currently set surface and stop using it.
489          *
490          * @param surface The surface to be used for interactive app UI rendering. Can be
491          *                {@code null}.
492          * @return {@code true} if the surface was set successfully, {@code false} otherwise.
493          */
onSetSurface(@ullable Surface surface)494         public abstract boolean onSetSurface(@Nullable Surface surface);
495 
496         /**
497          * Called after any structural changes (format or size) have been made to the surface passed
498          * in {@link #onSetSurface}. This method is always called at least once, after
499          * {@link #onSetSurface} is called with non-null surface.
500          *
501          * @param format The new {@link PixelFormat} of the surface.
502          * @param width The new width of the surface.
503          * @param height The new height of the surface.
504          */
onSurfaceChanged(@ixelFormat.Format int format, int width, int height)505         public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
506         }
507 
508         /**
509          * Called when the size of the media view is changed by the application.
510          *
511          * <p>This is always called at least once when the session is created regardless of whether
512          * the media view is enabled or not. The media view container size is the same as the
513          * containing {@link TvInteractiveAppView}. Note that the size of the underlying surface can
514          * be different if the surface was changed by calling {@link #layoutSurface}.
515          *
516          * @param width The width of the media view, in pixels.
517          * @param height The height of the media view, in pixels.
518          */
onMediaViewSizeChanged(@x int width, @Px int height)519         public void onMediaViewSizeChanged(@Px int width, @Px int height) {
520         }
521 
522         /**
523          * Called when the application requests to create an media view. Each session
524          * implementation can override this method and return its own view.
525          *
526          * @return a view attached to the media window
527          */
528         @Nullable
onCreateMediaView()529         public View onCreateMediaView() {
530             return null;
531         }
532 
533         /**
534          * Releases TvInteractiveAppService session.
535          */
onRelease()536         public abstract void onRelease();
537 
538         /**
539          * Called when the corresponding TV input tuned to a channel.
540          *
541          * @param channelUri The tuned channel URI.
542          */
onTuned(@onNull Uri channelUri)543         public void onTuned(@NonNull Uri channelUri) {
544         }
545 
546         /**
547          * Called when the corresponding TV input selected to a track.
548          */
onTrackSelected(@vTrackInfo.Type int type, @NonNull String trackId)549         public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) {
550         }
551 
552         /**
553          * Called when the tracks are changed.
554          */
onTracksChanged(@onNull List<TvTrackInfo> tracks)555         public void onTracksChanged(@NonNull List<TvTrackInfo> tracks) {
556         }
557 
558         /**
559          * Called when video is available.
560          */
onVideoAvailable()561         public void onVideoAvailable() {
562         }
563 
564         /**
565          * Called when video is unavailable.
566          */
onVideoUnavailable(@vInputManager.VideoUnavailableReason int reason)567         public void onVideoUnavailable(@TvInputManager.VideoUnavailableReason int reason) {
568         }
569 
570         /**
571          * Called when content is allowed.
572          */
onContentAllowed()573         public void onContentAllowed() {
574         }
575 
576         /**
577          * Called when content is blocked.
578          */
onContentBlocked(@onNull TvContentRating rating)579         public void onContentBlocked(@NonNull TvContentRating rating) {
580         }
581 
582         /**
583          * Called when signal strength is changed.
584          */
onSignalStrength(@vInputManager.SignalStrength int strength)585         public void onSignalStrength(@TvInputManager.SignalStrength int strength) {
586         }
587 
588         /**
589          * Called when a broadcast info response is received.
590          */
onBroadcastInfoResponse(@onNull BroadcastInfoResponse response)591         public void onBroadcastInfoResponse(@NonNull BroadcastInfoResponse response) {
592         }
593 
594         /**
595          * Called when an advertisement response is received.
596          */
onAdResponse(@onNull AdResponse response)597         public void onAdResponse(@NonNull AdResponse response) {
598         }
599 
600         @Override
onKeyDown(int keyCode, @NonNull KeyEvent event)601         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
602             return false;
603         }
604 
605         @Override
onKeyLongPress(int keyCode, @NonNull KeyEvent event)606         public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
607             return false;
608         }
609 
610         @Override
onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event)611         public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
612             return false;
613         }
614 
615         @Override
onKeyUp(int keyCode, @NonNull KeyEvent event)616         public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
617             return false;
618         }
619 
620         /**
621          * Implement this method to handle touch screen motion events on the current session.
622          *
623          * @param event The motion event being received.
624          * @return If you handled the event, return {@code true}. If you want to allow the event to
625          *         be handled by the next receiver, return {@code false}.
626          * @see View#onTouchEvent
627          */
onTouchEvent(@onNull MotionEvent event)628         public boolean onTouchEvent(@NonNull MotionEvent event) {
629             return false;
630         }
631 
632         /**
633          * Implement this method to handle trackball events on the current session.
634          *
635          * @param event The motion event being received.
636          * @return If you handled the event, return {@code true}. If you want to allow the event to
637          *         be handled by the next receiver, return {@code false}.
638          * @see View#onTrackballEvent
639          */
onTrackballEvent(@onNull MotionEvent event)640         public boolean onTrackballEvent(@NonNull MotionEvent event) {
641             return false;
642         }
643 
644         /**
645          * Implement this method to handle generic motion events on the current session.
646          *
647          * @param event The motion event being received.
648          * @return If you handled the event, return {@code true}. If you want to allow the event to
649          *         be handled by the next receiver, return {@code false}.
650          * @see View#onGenericMotionEvent
651          */
onGenericMotionEvent(@onNull MotionEvent event)652         public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
653             return false;
654         }
655 
656         /**
657          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
658          * is relative to the overlay view that sits on top of this surface.
659          *
660          * @param left Left position in pixels, relative to the overlay view.
661          * @param top Top position in pixels, relative to the overlay view.
662          * @param right Right position in pixels, relative to the overlay view.
663          * @param bottom Bottom position in pixels, relative to the overlay view.
664          */
665         @CallSuper
layoutSurface(final int left, final int top, final int right, final int bottom)666         public void layoutSurface(final int left, final int top, final int right,
667                 final int bottom) {
668             if (left > right || top > bottom) {
669                 throw new IllegalArgumentException("Invalid parameter");
670             }
671             executeOrPostRunnableOnMainThread(new Runnable() {
672                 @MainThread
673                 @Override
674                 public void run() {
675                     try {
676                         if (DEBUG) {
677                             Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
678                                     + ", r=" + right + ", b=" + bottom + ",)");
679                         }
680                         if (mSessionCallback != null) {
681                             mSessionCallback.onLayoutSurface(left, top, right, bottom);
682                         }
683                     } catch (RemoteException e) {
684                         Log.w(TAG, "error in layoutSurface", e);
685                     }
686                 }
687             });
688         }
689 
690         /**
691          * Requests broadcast related information from the related TV input.
692          * @param request the request for broadcast info
693          */
694         @CallSuper
requestBroadcastInfo(@onNull final BroadcastInfoRequest request)695         public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
696             executeOrPostRunnableOnMainThread(new Runnable() {
697                 @MainThread
698                 @Override
699                 public void run() {
700                     try {
701                         if (DEBUG) {
702                             Log.d(TAG, "requestBroadcastInfo (requestId="
703                                     + request.getRequestId() + ")");
704                         }
705                         if (mSessionCallback != null) {
706                             mSessionCallback.onBroadcastInfoRequest(request);
707                         }
708                     } catch (RemoteException e) {
709                         Log.w(TAG, "error in requestBroadcastInfo", e);
710                     }
711                 }
712             });
713         }
714 
715         /**
716          * Remove broadcast information request from the related TV input.
717          * @param requestId the ID of the request
718          */
719         @CallSuper
removeBroadcastInfo(final int requestId)720         public void removeBroadcastInfo(final int requestId) {
721             executeOrPostRunnableOnMainThread(new Runnable() {
722                 @MainThread
723                 @Override
724                 public void run() {
725                     try {
726                         if (DEBUG) {
727                             Log.d(TAG, "removeBroadcastInfo (requestId="
728                                     + requestId + ")");
729                         }
730                         if (mSessionCallback != null) {
731                             mSessionCallback.onRemoveBroadcastInfo(requestId);
732                         }
733                     } catch (RemoteException e) {
734                         Log.w(TAG, "error in removeBroadcastInfo", e);
735                     }
736                 }
737             });
738         }
739 
740         /**
741          * Sends a specific playback command to be processed by the related TV input.
742          *
743          * @param cmdType type of the specific command
744          * @param parameters parameters of the specific command
745          */
746         @CallSuper
sendPlaybackCommandRequest( @laybackCommandType @onNull String cmdType, @Nullable Bundle parameters)747         public void sendPlaybackCommandRequest(
748                 @PlaybackCommandType @NonNull String cmdType, @Nullable Bundle parameters) {
749             executeOrPostRunnableOnMainThread(new Runnable() {
750                 @MainThread
751                 @Override
752                 public void run() {
753                     try {
754                         if (DEBUG) {
755                             Log.d(TAG, "requestCommand (cmdType=" + cmdType + ", parameters="
756                                     + parameters.toString() + ")");
757                         }
758                         if (mSessionCallback != null) {
759                             mSessionCallback.onCommandRequest(cmdType, parameters);
760                         }
761                     } catch (RemoteException e) {
762                         Log.w(TAG, "error in requestCommand", e);
763                     }
764                 }
765             });
766         }
767 
768         /**
769          * Sets broadcast video bounds.
770          */
771         @CallSuper
setVideoBounds(@onNull Rect rect)772         public void setVideoBounds(@NonNull Rect rect) {
773             executeOrPostRunnableOnMainThread(new Runnable() {
774                 @MainThread
775                 @Override
776                 public void run() {
777                     try {
778                         if (DEBUG) {
779                             Log.d(TAG, "setVideoBounds (rect=" + rect + ")");
780                         }
781                         if (mSessionCallback != null) {
782                             mSessionCallback.onSetVideoBounds(rect);
783                         }
784                     } catch (RemoteException e) {
785                         Log.w(TAG, "error in setVideoBounds", e);
786                     }
787                 }
788             });
789         }
790 
791         /**
792          * Requests the URI of the current channel.
793          */
794         @CallSuper
requestCurrentChannelUri()795         public void requestCurrentChannelUri() {
796             executeOrPostRunnableOnMainThread(new Runnable() {
797                 @MainThread
798                 @Override
799                 public void run() {
800                     try {
801                         if (DEBUG) {
802                             Log.d(TAG, "requestCurrentChannelUri");
803                         }
804                         if (mSessionCallback != null) {
805                             mSessionCallback.onRequestCurrentChannelUri();
806                         }
807                     } catch (RemoteException e) {
808                         Log.w(TAG, "error in requestCurrentChannelUri", e);
809                     }
810                 }
811             });
812         }
813 
814         /**
815          * Requests the logic channel number (LCN) of the current channel.
816          */
817         @CallSuper
requestCurrentChannelLcn()818         public void requestCurrentChannelLcn() {
819             executeOrPostRunnableOnMainThread(new Runnable() {
820                 @MainThread
821                 @Override
822                 public void run() {
823                     try {
824                         if (DEBUG) {
825                             Log.d(TAG, "requestCurrentChannelLcn");
826                         }
827                         if (mSessionCallback != null) {
828                             mSessionCallback.onRequestCurrentChannelLcn();
829                         }
830                     } catch (RemoteException e) {
831                         Log.w(TAG, "error in requestCurrentChannelLcn", e);
832                     }
833                 }
834             });
835         }
836 
837         /**
838          * Requests stream volume.
839          */
840         @CallSuper
requestStreamVolume()841         public void requestStreamVolume() {
842             executeOrPostRunnableOnMainThread(new Runnable() {
843                 @MainThread
844                 @Override
845                 public void run() {
846                     try {
847                         if (DEBUG) {
848                             Log.d(TAG, "requestStreamVolume");
849                         }
850                         if (mSessionCallback != null) {
851                             mSessionCallback.onRequestStreamVolume();
852                         }
853                     } catch (RemoteException e) {
854                         Log.w(TAG, "error in requestStreamVolume", e);
855                     }
856                 }
857             });
858         }
859 
860         /**
861          * Requests the list of {@link TvTrackInfo}.
862          */
863         @CallSuper
requestTrackInfoList()864         public void requestTrackInfoList() {
865             executeOrPostRunnableOnMainThread(new Runnable() {
866                 @MainThread
867                 @Override
868                 public void run() {
869                     try {
870                         if (DEBUG) {
871                             Log.d(TAG, "requestTrackInfoList");
872                         }
873                         if (mSessionCallback != null) {
874                             mSessionCallback.onRequestTrackInfoList();
875                         }
876                     } catch (RemoteException e) {
877                         Log.w(TAG, "error in requestTrackInfoList", e);
878                     }
879                 }
880             });
881         }
882 
883         /**
884          * Requests current TV input ID.
885          *
886          * @see android.media.tv.TvInputInfo
887          */
888         @CallSuper
requestCurrentTvInputId()889         public void requestCurrentTvInputId() {
890             executeOrPostRunnableOnMainThread(new Runnable() {
891                 @MainThread
892                 @Override
893                 public void run() {
894                     try {
895                         if (DEBUG) {
896                             Log.d(TAG, "requestCurrentTvInputId");
897                         }
898                         if (mSessionCallback != null) {
899                             mSessionCallback.onRequestCurrentTvInputId();
900                         }
901                     } catch (RemoteException e) {
902                         Log.w(TAG, "error in requestCurrentTvInputId", e);
903                     }
904                 }
905             });
906         }
907 
908         /**
909          * Requests signing of the given data.
910          *
911          * <p>This is used when the corresponding server of the broadcast-independent interactive
912          * app requires signing during handshaking, and the interactive app service doesn't have
913          * the built-in private key. The private key is provided by the content providers and
914          * pre-built in the related app, such as TV app.
915          *
916          * @param signingId the ID to identify the request. When a result is received, this ID can
917          *                  be used to correlate the result with the request.
918          * @param algorithm the standard name of the signature algorithm requested, such as
919          *                  MD5withRSA, SHA256withDSA, etc. The name is from standards like
920          *                  FIPS PUB 186-4 and PKCS #1.
921          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
922          * @param data the original bytes to be signed.
923          *
924          * @see #onSigningResult(String, byte[])
925          * @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
926          * @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
927          */
928         @CallSuper
requestSigning(@onNull String signingId, @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data)929         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
930                 @NonNull String alias, @NonNull byte[] data) {
931             executeOrPostRunnableOnMainThread(new Runnable() {
932                 @MainThread
933                 @Override
934                 public void run() {
935                     try {
936                         if (DEBUG) {
937                             Log.d(TAG, "requestSigning");
938                         }
939                         if (mSessionCallback != null) {
940                             mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
941                         }
942                     } catch (RemoteException e) {
943                         Log.w(TAG, "error in requestSigning", e);
944                     }
945                 }
946             });
947         }
948 
949         /**
950          * Sends an advertisement request to be processed by the related TV input.
951          *
952          * @param request The advertisement request
953          */
954         @CallSuper
requestAd(@onNull final AdRequest request)955         public void requestAd(@NonNull final AdRequest request) {
956             executeOrPostRunnableOnMainThread(new Runnable() {
957                 @MainThread
958                 @Override
959                 public void run() {
960                     try {
961                         if (DEBUG) {
962                             Log.d(TAG, "requestAd (id=" + request.getId() + ")");
963                         }
964                         if (mSessionCallback != null) {
965                             mSessionCallback.onAdRequest(request);
966                         }
967                     } catch (RemoteException e) {
968                         Log.w(TAG, "error in requestAd", e);
969                     }
970                 }
971             });
972         }
973 
startInteractiveApp()974         void startInteractiveApp() {
975             onStartInteractiveApp();
976         }
977 
stopInteractiveApp()978         void stopInteractiveApp() {
979             onStopInteractiveApp();
980         }
981 
resetInteractiveApp()982         void resetInteractiveApp() {
983             onResetInteractiveApp();
984         }
985 
createBiInteractiveApp(@onNull Uri biIAppUri, @Nullable Bundle params)986         void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
987             onCreateBiInteractiveAppRequest(biIAppUri, params);
988         }
989 
destroyBiInteractiveApp(@onNull String biIAppId)990         void destroyBiInteractiveApp(@NonNull String biIAppId) {
991             onDestroyBiInteractiveAppRequest(biIAppId);
992         }
993 
setTeletextAppEnabled(boolean enable)994         void setTeletextAppEnabled(boolean enable) {
995             onSetTeletextAppEnabled(enable);
996         }
997 
sendCurrentChannelUri(@ullable Uri channelUri)998         void sendCurrentChannelUri(@Nullable Uri channelUri) {
999             onCurrentChannelUri(channelUri);
1000         }
1001 
sendCurrentChannelLcn(int lcn)1002         void sendCurrentChannelLcn(int lcn) {
1003             onCurrentChannelLcn(lcn);
1004         }
1005 
sendStreamVolume(float volume)1006         void sendStreamVolume(float volume) {
1007             onStreamVolume(volume);
1008         }
1009 
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1010         void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
1011             onTrackInfoList(tracks);
1012         }
1013 
sendCurrentTvInputId(@ullable String inputId)1014         void sendCurrentTvInputId(@Nullable String inputId) {
1015             onCurrentTvInputId(inputId);
1016         }
1017 
sendSigningResult(String signingId, byte[] result)1018         void sendSigningResult(String signingId, byte[] result) {
1019             onSigningResult(signingId, result);
1020         }
1021 
notifyError(String errMsg, Bundle params)1022         void notifyError(String errMsg, Bundle params) {
1023             onError(errMsg, params);
1024         }
1025 
release()1026         void release() {
1027             onRelease();
1028             if (mSurface != null) {
1029                 mSurface.release();
1030                 mSurface = null;
1031             }
1032             synchronized (mLock) {
1033                 mSessionCallback = null;
1034                 mPendingActions.clear();
1035             }
1036             // Removes the media view lastly so that any hanging on the main thread can be handled
1037             // in {@link #scheduleMediaViewCleanup}.
1038             removeMediaView(true);
1039         }
1040 
notifyTuned(Uri channelUri)1041         void notifyTuned(Uri channelUri) {
1042             if (DEBUG) {
1043                 Log.d(TAG, "notifyTuned (channelUri=" + channelUri + ")");
1044             }
1045             onTuned(channelUri);
1046         }
1047 
notifyTrackSelected(int type, String trackId)1048         void notifyTrackSelected(int type, String trackId) {
1049             if (DEBUG) {
1050                 Log.d(TAG, "notifyTrackSelected (type=" + type + "trackId=" + trackId + ")");
1051             }
1052             onTrackSelected(type, trackId);
1053         }
1054 
notifyTracksChanged(List<TvTrackInfo> tracks)1055         void notifyTracksChanged(List<TvTrackInfo> tracks) {
1056             if (DEBUG) {
1057                 Log.d(TAG, "notifyTracksChanged (tracks=" + tracks + ")");
1058             }
1059             onTracksChanged(tracks);
1060         }
1061 
notifyVideoAvailable()1062         void notifyVideoAvailable() {
1063             if (DEBUG) {
1064                 Log.d(TAG, "notifyVideoAvailable");
1065             }
1066             onVideoAvailable();
1067         }
1068 
notifyVideoUnavailable(int reason)1069         void notifyVideoUnavailable(int reason) {
1070             if (DEBUG) {
1071                 Log.d(TAG, "notifyVideoAvailable (reason=" + reason + ")");
1072             }
1073             onVideoUnavailable(reason);
1074         }
1075 
notifyContentAllowed()1076         void notifyContentAllowed() {
1077             if (DEBUG) {
1078                 Log.d(TAG, "notifyContentAllowed");
1079             }
1080             onContentAllowed();
1081         }
1082 
notifyContentBlocked(TvContentRating rating)1083         void notifyContentBlocked(TvContentRating rating) {
1084             if (DEBUG) {
1085                 Log.d(TAG, "notifyContentBlocked (rating=" + rating.flattenToString() + ")");
1086             }
1087             onContentBlocked(rating);
1088         }
1089 
notifySignalStrength(int strength)1090         void notifySignalStrength(int strength) {
1091             if (DEBUG) {
1092                 Log.d(TAG, "notifySignalStrength (strength=" + strength + ")");
1093             }
1094             onSignalStrength(strength);
1095         }
1096 
1097         /**
1098          * Calls {@link #onBroadcastInfoResponse}.
1099          */
notifyBroadcastInfoResponse(BroadcastInfoResponse response)1100         void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
1101             if (DEBUG) {
1102                 Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
1103                         + response.getRequestId() + ")");
1104             }
1105             onBroadcastInfoResponse(response);
1106         }
1107 
1108         /**
1109          * Calls {@link #onAdResponse}.
1110          */
notifyAdResponse(AdResponse response)1111         void notifyAdResponse(AdResponse response) {
1112             if (DEBUG) {
1113                 Log.d(TAG, "notifyAdResponse (requestId=" + response.getId() + ")");
1114             }
1115             onAdResponse(response);
1116         }
1117 
1118         /**
1119          * Notifies when the session state is changed.
1120          *
1121          * @param state the current session state.
1122          * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
1123          *            used when the state is not
1124          *            {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
1125          */
1126         @CallSuper
notifySessionStateChanged( @vInteractiveAppManager.InteractiveAppState int state, @TvInteractiveAppManager.ErrorCode int err)1127         public void notifySessionStateChanged(
1128                 @TvInteractiveAppManager.InteractiveAppState int state,
1129                 @TvInteractiveAppManager.ErrorCode int err) {
1130             executeOrPostRunnableOnMainThread(new Runnable() {
1131                 @MainThread
1132                 @Override
1133                 public void run() {
1134                     try {
1135                         if (DEBUG) {
1136                             Log.d(TAG, "notifySessionStateChanged (state="
1137                                     + state + "; err=" + err + ")");
1138                         }
1139                         if (mSessionCallback != null) {
1140                             mSessionCallback.onSessionStateChanged(state, err);
1141                         }
1142                     } catch (RemoteException e) {
1143                         Log.w(TAG, "error in notifySessionStateChanged", e);
1144                     }
1145                 }
1146             });
1147         }
1148 
1149         /**
1150          * Notifies the broadcast-independent(BI) interactive application has been created.
1151          *
1152          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
1153          *                 app. {@code null} if it's not created successfully.
1154          *
1155          * @see #onCreateBiInteractiveAppRequest(Uri, Bundle)
1156          */
1157         @CallSuper
notifyBiInteractiveAppCreated( @onNull Uri biIAppUri, @Nullable String biIAppId)1158         public final void notifyBiInteractiveAppCreated(
1159                 @NonNull Uri biIAppUri, @Nullable String biIAppId) {
1160             executeOrPostRunnableOnMainThread(new Runnable() {
1161                 @MainThread
1162                 @Override
1163                 public void run() {
1164                     try {
1165                         if (DEBUG) {
1166                             Log.d(TAG, "notifyBiInteractiveAppCreated (biIAppId="
1167                                     + biIAppId + ")");
1168                         }
1169                         if (mSessionCallback != null) {
1170                             mSessionCallback.onBiInteractiveAppCreated(biIAppUri, biIAppId);
1171                         }
1172                     } catch (RemoteException e) {
1173                         Log.w(TAG, "error in notifyBiInteractiveAppCreated", e);
1174                     }
1175                 }
1176             });
1177         }
1178 
1179         /**
1180          * Notifies when the digital teletext app state is changed.
1181          * @param state the current state.
1182          */
1183         @CallSuper
notifyTeletextAppStateChanged( @vInteractiveAppManager.TeletextAppState int state)1184         public final void notifyTeletextAppStateChanged(
1185                 @TvInteractiveAppManager.TeletextAppState int state) {
1186             executeOrPostRunnableOnMainThread(new Runnable() {
1187                 @MainThread
1188                 @Override
1189                 public void run() {
1190                     try {
1191                         if (DEBUG) {
1192                             Log.d(TAG, "notifyTeletextAppState (state="
1193                                     + state + ")");
1194                         }
1195                         if (mSessionCallback != null) {
1196                             mSessionCallback.onTeletextAppStateChanged(state);
1197                         }
1198                     } catch (RemoteException e) {
1199                         Log.w(TAG, "error in notifyTeletextAppState", e);
1200                     }
1201                 }
1202             });
1203         }
1204 
1205         /**
1206          * Takes care of dispatching incoming input events and tells whether the event was handled.
1207          */
dispatchInputEvent(InputEvent event, InputEventReceiver receiver)1208         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
1209             if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
1210             if (event instanceof KeyEvent) {
1211                 KeyEvent keyEvent = (KeyEvent) event;
1212                 if (keyEvent.dispatch(this, mDispatcherState, this)) {
1213                     return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
1214                 }
1215 
1216                 // TODO: special handlings of navigation keys and media keys
1217             } else if (event instanceof MotionEvent) {
1218                 MotionEvent motionEvent = (MotionEvent) event;
1219                 final int source = motionEvent.getSource();
1220                 if (motionEvent.isTouchEvent()) {
1221                     if (onTouchEvent(motionEvent)) {
1222                         return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
1223                     }
1224                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
1225                     if (onTrackballEvent(motionEvent)) {
1226                         return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
1227                     }
1228                 } else {
1229                     if (onGenericMotionEvent(motionEvent)) {
1230                         return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
1231                     }
1232                 }
1233             }
1234             // TODO: handle overlay view
1235             return TvInteractiveAppManager.Session.DISPATCH_NOT_HANDLED;
1236         }
1237 
initialize(ITvInteractiveAppSessionCallback callback)1238         private void initialize(ITvInteractiveAppSessionCallback callback) {
1239             synchronized (mLock) {
1240                 mSessionCallback = callback;
1241                 for (Runnable runnable : mPendingActions) {
1242                     runnable.run();
1243                 }
1244                 mPendingActions.clear();
1245             }
1246         }
1247 
1248         /**
1249          * Calls {@link #onSetSurface}.
1250          */
setSurface(Surface surface)1251         void setSurface(Surface surface) {
1252             onSetSurface(surface);
1253             if (mSurface != null) {
1254                 mSurface.release();
1255             }
1256             mSurface = surface;
1257             // TODO: Handle failure.
1258         }
1259 
1260         /**
1261          * Calls {@link #onSurfaceChanged}.
1262          */
dispatchSurfaceChanged(int format, int width, int height)1263         void dispatchSurfaceChanged(int format, int width, int height) {
1264             if (DEBUG) {
1265                 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
1266                         + ", height=" + height + ")");
1267             }
1268             onSurfaceChanged(format, width, height);
1269         }
1270 
executeOrPostRunnableOnMainThread(Runnable action)1271         private void executeOrPostRunnableOnMainThread(Runnable action) {
1272             synchronized (mLock) {
1273                 if (mSessionCallback == null) {
1274                     // The session is not initialized yet.
1275                     mPendingActions.add(action);
1276                 } else {
1277                     if (mHandler.getLooper().isCurrentThread()) {
1278                         action.run();
1279                     } else {
1280                         // Posts the runnable if this is not called from the main thread
1281                         mHandler.post(action);
1282                     }
1283                 }
1284             }
1285         }
1286 
1287         /**
1288          * Creates an media view. This calls {@link #onCreateMediaView} to get a view to attach
1289          * to the media window.
1290          *
1291          * @param windowToken A window token of the application.
1292          * @param frame A position of the media view.
1293          */
createMediaView(IBinder windowToken, Rect frame)1294         void createMediaView(IBinder windowToken, Rect frame) {
1295             if (mMediaViewContainer != null) {
1296                 removeMediaView(false);
1297             }
1298             if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
1299             mWindowToken = windowToken;
1300             mMediaFrame = frame;
1301             onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
1302             if (!mMediaViewEnabled) {
1303                 return;
1304             }
1305             mMediaView = onCreateMediaView();
1306             if (mMediaView == null) {
1307                 return;
1308             }
1309             if (mMediaViewCleanUpTask != null) {
1310                 mMediaViewCleanUpTask.cancel(true);
1311                 mMediaViewCleanUpTask = null;
1312             }
1313             // Creates a container view to check hanging on the media view detaching.
1314             // Adding/removing the media view to/from the container make the view attach/detach
1315             // logic run on the main thread.
1316             mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
1317             mMediaViewContainer.addView(mMediaView);
1318 
1319             int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
1320             // We make the overlay view non-focusable and non-touchable so that
1321             // the application that owns the window token can decide whether to consume or
1322             // dispatch the input events.
1323             int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1324                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
1325                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1326             if (ActivityManager.isHighEndGfx()) {
1327                 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
1328             }
1329             mWindowParams = new WindowManager.LayoutParams(
1330                     frame.right - frame.left, frame.bottom - frame.top,
1331                     frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
1332             mWindowParams.privateFlags |=
1333                     WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
1334             mWindowParams.gravity = Gravity.START | Gravity.TOP;
1335             mWindowParams.token = windowToken;
1336             mWindowManager.addView(mMediaViewContainer, mWindowParams);
1337         }
1338 
1339         /**
1340          * Relayouts the current media view.
1341          *
1342          * @param frame A new position of the media view.
1343          */
relayoutMediaView(Rect frame)1344         void relayoutMediaView(Rect frame) {
1345             if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
1346             if (mMediaFrame == null || mMediaFrame.width() != frame.width()
1347                     || mMediaFrame.height() != frame.height()) {
1348                 // Note: relayoutMediaView is called whenever TvInteractiveAppView's layout is
1349                 // changed regardless of setMediaViewEnabled.
1350                 onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
1351             }
1352             mMediaFrame = frame;
1353             if (!mMediaViewEnabled || mMediaViewContainer == null) {
1354                 return;
1355             }
1356             mWindowParams.x = frame.left;
1357             mWindowParams.y = frame.top;
1358             mWindowParams.width = frame.right - frame.left;
1359             mWindowParams.height = frame.bottom - frame.top;
1360             mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
1361         }
1362 
1363         /**
1364          * Removes the current media view.
1365          */
removeMediaView(boolean clearWindowToken)1366         void removeMediaView(boolean clearWindowToken) {
1367             if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
1368             if (clearWindowToken) {
1369                 mWindowToken = null;
1370                 mMediaFrame = null;
1371             }
1372             if (mMediaViewContainer != null) {
1373                 // Removes the media view from the view hierarchy in advance so that it can be
1374                 // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
1375                 // hanging.
1376                 mMediaViewContainer.removeView(mMediaView);
1377                 mMediaView = null;
1378                 mWindowManager.removeView(mMediaViewContainer);
1379                 mMediaViewContainer = null;
1380                 mWindowParams = null;
1381             }
1382         }
1383 
1384         /**
1385          * Schedules a task which checks whether the media view is detached and kills the process
1386          * if it is not. Note that this method is expected to be called in a non-main thread.
1387          */
scheduleMediaViewCleanup()1388         void scheduleMediaViewCleanup() {
1389             View mediaViewParent = mMediaViewContainer;
1390             if (mediaViewParent != null) {
1391                 mMediaViewCleanUpTask = new MediaViewCleanUpTask();
1392                 mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
1393                         mediaViewParent);
1394             }
1395         }
1396     }
1397 
1398     private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
1399         @Override
doInBackground(View... views)1400         protected Void doInBackground(View... views) {
1401             View mediaViewParent = views[0];
1402             try {
1403                 Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
1404             } catch (InterruptedException e) {
1405                 return null;
1406             }
1407             if (isCancelled()) {
1408                 return null;
1409             }
1410             if (mediaViewParent.isAttachedToWindow()) {
1411                 Log.e(TAG, "Time out on releasing media view. Killing "
1412                         + mediaViewParent.getContext().getPackageName());
1413                 android.os.Process.killProcess(Process.myPid());
1414             }
1415             return null;
1416         }
1417     }
1418 
1419     /**
1420      * Implements the internal ITvInteractiveAppSession interface.
1421      * @hide
1422      */
1423     public static class ITvInteractiveAppSessionWrapper extends ITvInteractiveAppSession.Stub {
1424         // TODO: put ITvInteractiveAppSessionWrapper in a separate Java file
1425         private final Session mSessionImpl;
1426         private InputChannel mChannel;
1427         private TvInteractiveAppEventReceiver mReceiver;
1428 
ITvInteractiveAppSessionWrapper( Context context, Session mSessionImpl, InputChannel channel)1429         public ITvInteractiveAppSessionWrapper(
1430                 Context context, Session mSessionImpl, InputChannel channel) {
1431             this.mSessionImpl = mSessionImpl;
1432             mChannel = channel;
1433             if (channel != null) {
1434                 mReceiver = new TvInteractiveAppEventReceiver(channel, context.getMainLooper());
1435             }
1436         }
1437 
1438         @Override
startInteractiveApp()1439         public void startInteractiveApp() {
1440             mSessionImpl.startInteractiveApp();
1441         }
1442 
1443         @Override
stopInteractiveApp()1444         public void stopInteractiveApp() {
1445             mSessionImpl.stopInteractiveApp();
1446         }
1447 
1448         @Override
resetInteractiveApp()1449         public void resetInteractiveApp() {
1450             mSessionImpl.resetInteractiveApp();
1451         }
1452 
1453         @Override
createBiInteractiveApp(@onNull Uri biIAppUri, @Nullable Bundle params)1454         public void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
1455             mSessionImpl.createBiInteractiveApp(biIAppUri, params);
1456         }
1457 
1458         @Override
setTeletextAppEnabled(boolean enable)1459         public void setTeletextAppEnabled(boolean enable) {
1460             mSessionImpl.setTeletextAppEnabled(enable);
1461         }
1462 
1463         @Override
destroyBiInteractiveApp(@onNull String biIAppId)1464         public void destroyBiInteractiveApp(@NonNull String biIAppId) {
1465             mSessionImpl.destroyBiInteractiveApp(biIAppId);
1466         }
1467 
1468         @Override
sendCurrentChannelUri(@ullable Uri channelUri)1469         public void sendCurrentChannelUri(@Nullable Uri channelUri) {
1470             mSessionImpl.sendCurrentChannelUri(channelUri);
1471         }
1472 
1473         @Override
sendCurrentChannelLcn(int lcn)1474         public void sendCurrentChannelLcn(int lcn) {
1475             mSessionImpl.sendCurrentChannelLcn(lcn);
1476         }
1477 
1478         @Override
sendStreamVolume(float volume)1479         public void sendStreamVolume(float volume) {
1480             mSessionImpl.sendStreamVolume(volume);
1481         }
1482 
1483         @Override
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1484         public void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
1485             mSessionImpl.sendTrackInfoList(tracks);
1486         }
1487 
1488         @Override
sendCurrentTvInputId(@ullable String inputId)1489         public void sendCurrentTvInputId(@Nullable String inputId) {
1490             mSessionImpl.sendCurrentTvInputId(inputId);
1491         }
1492 
1493         @Override
sendSigningResult(@onNull String signingId, @NonNull byte[] result)1494         public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
1495             mSessionImpl.sendSigningResult(signingId, result);
1496         }
1497 
1498         @Override
notifyError(@onNull String errMsg, @NonNull Bundle params)1499         public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
1500             mSessionImpl.notifyError(errMsg, params);
1501         }
1502 
1503         @Override
release()1504         public void release() {
1505             mSessionImpl.scheduleMediaViewCleanup();
1506             mSessionImpl.release();
1507         }
1508 
1509         @Override
notifyTuned(Uri channelUri)1510         public void notifyTuned(Uri channelUri) {
1511             mSessionImpl.notifyTuned(channelUri);
1512         }
1513 
1514         @Override
notifyTrackSelected(int type, final String trackId)1515         public void notifyTrackSelected(int type, final String trackId) {
1516             mSessionImpl.notifyTrackSelected(type, trackId);
1517         }
1518 
1519         @Override
notifyTracksChanged(List<TvTrackInfo> tracks)1520         public void notifyTracksChanged(List<TvTrackInfo> tracks) {
1521             mSessionImpl.notifyTracksChanged(tracks);
1522         }
1523 
1524         @Override
notifyVideoAvailable()1525         public void notifyVideoAvailable() {
1526             mSessionImpl.notifyVideoAvailable();
1527         }
1528 
1529         @Override
notifyVideoUnavailable(int reason)1530         public void notifyVideoUnavailable(int reason) {
1531             mSessionImpl.notifyVideoUnavailable(reason);
1532         }
1533 
1534         @Override
notifyContentAllowed()1535         public void notifyContentAllowed() {
1536             mSessionImpl.notifyContentAllowed();
1537         }
1538 
1539         @Override
notifyContentBlocked(String rating)1540         public void notifyContentBlocked(String rating) {
1541             mSessionImpl.notifyContentBlocked(TvContentRating.unflattenFromString(rating));
1542         }
1543 
1544         @Override
notifySignalStrength(int strength)1545         public void notifySignalStrength(int strength) {
1546             mSessionImpl.notifySignalStrength(strength);
1547         }
1548 
1549         @Override
setSurface(Surface surface)1550         public void setSurface(Surface surface) {
1551             mSessionImpl.setSurface(surface);
1552         }
1553 
1554         @Override
dispatchSurfaceChanged(int format, int width, int height)1555         public void dispatchSurfaceChanged(int format, int width, int height) {
1556             mSessionImpl.dispatchSurfaceChanged(format, width, height);
1557         }
1558 
1559         @Override
notifyBroadcastInfoResponse(BroadcastInfoResponse response)1560         public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
1561             mSessionImpl.notifyBroadcastInfoResponse(response);
1562         }
1563 
1564         @Override
notifyAdResponse(AdResponse response)1565         public void notifyAdResponse(AdResponse response) {
1566             mSessionImpl.notifyAdResponse(response);
1567         }
1568 
1569         @Override
createMediaView(IBinder windowToken, Rect frame)1570         public void createMediaView(IBinder windowToken, Rect frame) {
1571             mSessionImpl.createMediaView(windowToken, frame);
1572         }
1573 
1574         @Override
relayoutMediaView(Rect frame)1575         public void relayoutMediaView(Rect frame) {
1576             mSessionImpl.relayoutMediaView(frame);
1577         }
1578 
1579         @Override
removeMediaView()1580         public void removeMediaView() {
1581             mSessionImpl.removeMediaView(true);
1582         }
1583 
1584         private final class TvInteractiveAppEventReceiver extends InputEventReceiver {
TvInteractiveAppEventReceiver(InputChannel inputChannel, Looper looper)1585             TvInteractiveAppEventReceiver(InputChannel inputChannel, Looper looper) {
1586                 super(inputChannel, looper);
1587             }
1588 
1589             @Override
onInputEvent(InputEvent event)1590             public void onInputEvent(InputEvent event) {
1591                 if (mSessionImpl == null) {
1592                     // The session has been finished.
1593                     finishInputEvent(event, false);
1594                     return;
1595                 }
1596 
1597                 int handled = mSessionImpl.dispatchInputEvent(event, this);
1598                 if (handled != TvInteractiveAppManager.Session.DISPATCH_IN_PROGRESS) {
1599                     finishInputEvent(
1600                             event, handled == TvInteractiveAppManager.Session.DISPATCH_HANDLED);
1601                 }
1602             }
1603         }
1604     }
1605 
1606     @SuppressLint("HandlerLeak")
1607     private final class ServiceHandler extends Handler {
1608         private static final int DO_CREATE_SESSION = 1;
1609         private static final int DO_NOTIFY_SESSION_CREATED = 2;
1610         private static final int DO_NOTIFY_RTE_STATE_CHANGED = 3;
1611 
broadcastRteStateChanged(int type, int state, int error)1612         private void broadcastRteStateChanged(int type, int state, int error) {
1613             int n = mCallbacks.beginBroadcast();
1614             for (int i = 0; i < n; ++i) {
1615                 try {
1616                     mCallbacks.getBroadcastItem(i).onStateChanged(type, state, error);
1617                 } catch (RemoteException e) {
1618                     Log.e(TAG, "error in broadcastRteStateChanged", e);
1619                 }
1620             }
1621             mCallbacks.finishBroadcast();
1622         }
1623 
1624         @Override
handleMessage(Message msg)1625         public void handleMessage(Message msg) {
1626             switch (msg.what) {
1627                 case DO_CREATE_SESSION: {
1628                     SomeArgs args = (SomeArgs) msg.obj;
1629                     InputChannel channel = (InputChannel) args.arg1;
1630                     ITvInteractiveAppSessionCallback cb =
1631                             (ITvInteractiveAppSessionCallback) args.arg2;
1632                     String iAppServiceId = (String) args.arg3;
1633                     int type = (int) args.arg4;
1634                     args.recycle();
1635                     Session sessionImpl = onCreateSession(iAppServiceId, type);
1636                     if (sessionImpl == null) {
1637                         try {
1638                             // Failed to create a session.
1639                             cb.onSessionCreated(null);
1640                         } catch (RemoteException e) {
1641                             Log.e(TAG, "error in onSessionCreated", e);
1642                         }
1643                         return;
1644                     }
1645                     ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper(
1646                             TvInteractiveAppService.this, sessionImpl, channel);
1647 
1648                     SomeArgs someArgs = SomeArgs.obtain();
1649                     someArgs.arg1 = sessionImpl;
1650                     someArgs.arg2 = stub;
1651                     someArgs.arg3 = cb;
1652                     mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
1653                             someArgs).sendToTarget();
1654                     return;
1655                 }
1656                 case DO_NOTIFY_SESSION_CREATED: {
1657                     SomeArgs args = (SomeArgs) msg.obj;
1658                     Session sessionImpl = (Session) args.arg1;
1659                     ITvInteractiveAppSession stub = (ITvInteractiveAppSession) args.arg2;
1660                     ITvInteractiveAppSessionCallback cb =
1661                             (ITvInteractiveAppSessionCallback) args.arg3;
1662                     try {
1663                         cb.onSessionCreated(stub);
1664                     } catch (RemoteException e) {
1665                         Log.e(TAG, "error in onSessionCreated", e);
1666                     }
1667                     if (sessionImpl != null) {
1668                         sessionImpl.initialize(cb);
1669                     }
1670                     args.recycle();
1671                     return;
1672                 }
1673                 case DO_NOTIFY_RTE_STATE_CHANGED: {
1674                     SomeArgs args = (SomeArgs) msg.obj;
1675                     int type = (int) args.arg1;
1676                     int state = (int) args.arg2;
1677                     int error = (int) args.arg3;
1678                     broadcastRteStateChanged(type, state, error);
1679                     return;
1680                 }
1681                 default: {
1682                     Log.w(TAG, "Unhandled message code: " + msg.what);
1683                     return;
1684                 }
1685             }
1686         }
1687 
1688     }
1689 }
1690