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.IntDef;
21 import android.annotation.MainThread;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.Px;
25 import android.annotation.SdkConstant;
26 import android.annotation.StringDef;
27 import android.annotation.SuppressLint;
28 import android.app.ActivityManager;
29 import android.app.Service;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.graphics.PixelFormat;
33 import android.graphics.Rect;
34 import android.media.PlaybackParams;
35 import android.media.tv.AdBuffer;
36 import android.media.tv.AdRequest;
37 import android.media.tv.AdResponse;
38 import android.media.tv.BroadcastInfoRequest;
39 import android.media.tv.BroadcastInfoResponse;
40 import android.media.tv.TvContentRating;
41 import android.media.tv.TvContract;
42 import android.media.tv.TvInputInfo;
43 import android.media.tv.TvInputManager;
44 import android.media.tv.TvRecordingInfo;
45 import android.media.tv.TvTrackInfo;
46 import android.media.tv.TvView;
47 import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
48 import android.net.Uri;
49 import android.os.AsyncTask;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Message;
54 import android.os.Process;
55 import android.os.RemoteCallbackList;
56 import android.os.RemoteException;
57 import android.util.Log;
58 import android.view.Gravity;
59 import android.view.InputChannel;
60 import android.view.InputDevice;
61 import android.view.InputEvent;
62 import android.view.InputEventReceiver;
63 import android.view.KeyEvent;
64 import android.view.MotionEvent;
65 import android.view.Surface;
66 import android.view.View;
67 import android.view.WindowManager;
68 import android.widget.FrameLayout;
69 
70 import com.android.internal.os.SomeArgs;
71 
72 import java.io.IOException;
73 import java.lang.annotation.Retention;
74 import java.lang.annotation.RetentionPolicy;
75 import java.util.ArrayList;
76 import java.util.List;
77 
78 /**
79  * A TV interactive application service is a service that provides runtime environment and runs TV
80  * interactive applications.
81  */
82 public abstract class TvInteractiveAppService extends Service {
83     private static final boolean DEBUG = false;
84     private static final String TAG = "TvInteractiveAppService";
85 
86     private static final int DETACH_MEDIA_VIEW_TIMEOUT_MS = 5000;
87 
88     /**
89      * This is the interface name that a service implementing a TV Interactive App service should
90      * say that it supports -- that is, this is the action it uses for its intent filter. To be
91      * supported, the service must also require the
92      * {@link android.Manifest.permission#BIND_TV_INTERACTIVE_APP} permission so that other
93      * applications cannot abuse it.
94      */
95     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
96     public static final String SERVICE_INTERFACE =
97             "android.media.tv.interactive.TvInteractiveAppService";
98 
99     /**
100      * Name under which a TvInteractiveAppService component publishes information about itself. This
101      * meta-data must reference an XML resource containing an
102      * <code>&lt;{@link android.R.styleable#TvInteractiveAppService tv-interactive-app}&gt;</code>
103      * tag.
104      */
105     public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
106 
107     /** @hide */
108     @Retention(RetentionPolicy.SOURCE)
109     @StringDef(prefix = "PLAYBACK_COMMAND_TYPE_", value = {
110             PLAYBACK_COMMAND_TYPE_TUNE,
111             PLAYBACK_COMMAND_TYPE_TUNE_NEXT,
112             PLAYBACK_COMMAND_TYPE_TUNE_PREV,
113             PLAYBACK_COMMAND_TYPE_STOP,
114             PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
115             PLAYBACK_COMMAND_TYPE_SELECT_TRACK
116     })
117     public @interface PlaybackCommandType {}
118 
119     /**
120      * Playback command type: tune to the given channel.
121      * @see #COMMAND_PARAMETER_KEY_CHANNEL_URI
122      */
123     public static final String PLAYBACK_COMMAND_TYPE_TUNE = "tune";
124     /**
125      * Playback command type: tune to the next channel.
126      */
127     public static final String PLAYBACK_COMMAND_TYPE_TUNE_NEXT = "tune_next";
128     /**
129      * Playback command type: tune to the previous channel.
130      */
131     public static final String PLAYBACK_COMMAND_TYPE_TUNE_PREV = "tune_previous";
132     /**
133      * Playback command type: stop the playback.
134      */
135     public static final String PLAYBACK_COMMAND_TYPE_STOP = "stop";
136     /**
137      * Playback command type: set the volume.
138      */
139     public static final String PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME =
140             "set_stream_volume";
141     /**
142      * Playback command type: select the given track.
143      */
144     public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
145 
146 
147 
148     /** @hide */
149     @Retention(RetentionPolicy.SOURCE)
150     @IntDef(prefix = "COMMAND_PARAMETER_VALUE_STOP_MODE_", value = {
151             COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK,
152             COMMAND_PARAMETER_VALUE_STOP_MODE_FREEZE
153     })
154     public @interface PlaybackCommandStopMode {}
155 
156     /**
157      * Playback command stop mode: show a blank screen.
158      * @see #PLAYBACK_COMMAND_TYPE_STOP
159      */
160     public static final int COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK = 1;
161 
162     /**
163      * Playback command stop mode: freeze the video.
164      * @see #PLAYBACK_COMMAND_TYPE_STOP
165      */
166     public static final int COMMAND_PARAMETER_VALUE_STOP_MODE_FREEZE = 2;
167 
168     /**
169      * Playback command parameter: stop mode.
170      * <p>Type: int
171      *
172      * @see #PLAYBACK_COMMAND_TYPE_STOP
173      */
174     public static final String COMMAND_PARAMETER_KEY_STOP_MODE = "command_stop_mode";
175 
176     /**
177      * Playback command parameter: channel URI.
178      * <p>Type: android.net.Uri
179      *
180      * @see #PLAYBACK_COMMAND_TYPE_TUNE
181      */
182     public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
183     /**
184      * Playback command parameter: TV input ID.
185      * <p>Type: String
186      *
187      * @see TvInputInfo#getId()
188      */
189     public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
190     /**
191      * Playback command parameter: stream volume.
192      * <p>Type: float
193      *
194      * @see #PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME
195      */
196     public static final String COMMAND_PARAMETER_KEY_VOLUME = "command_volume";
197     /**
198      * Playback command parameter: track type.
199      * <p>Type: int
200      *
201      * @see #PLAYBACK_COMMAND_TYPE_SELECT_TRACK
202      * @see TvTrackInfo#getType()
203      */
204     public static final String COMMAND_PARAMETER_KEY_TRACK_TYPE = "command_track_type";
205     /**
206      * Playback command parameter: track ID.
207      * <p>Type: String
208      *
209      * @see #PLAYBACK_COMMAND_TYPE_SELECT_TRACK
210      * @see TvTrackInfo#getId()
211      */
212     public static final String COMMAND_PARAMETER_KEY_TRACK_ID = "command_track_id";
213     /**
214      * Command to quiet channel change. No channel banner or channel info is shown.
215      * <p>Refer to HbbTV Spec 2.0.4 chapter A.2.4.3.
216      */
217     public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY =
218             "command_change_channel_quietly";
219 
220     /** @hide */
221     @Retention(RetentionPolicy.SOURCE)
222     @StringDef(prefix = "TIME_SHIFT_COMMAND_TYPE_", value = {
223             TIME_SHIFT_COMMAND_TYPE_PLAY,
224             TIME_SHIFT_COMMAND_TYPE_PAUSE,
225             TIME_SHIFT_COMMAND_TYPE_RESUME,
226             TIME_SHIFT_COMMAND_TYPE_SEEK_TO,
227             TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS,
228             TIME_SHIFT_COMMAND_TYPE_SET_MODE,
229     })
230     public @interface TimeShiftCommandType {}
231 
232     /**
233      * Time shift command type: play.
234      *
235      * @see TvView#timeShiftPlay(String, Uri)
236      */
237     public static final String TIME_SHIFT_COMMAND_TYPE_PLAY = "play";
238     /**
239      * Time shift command type: pause.
240      *
241      * @see TvView#timeShiftPause()
242      */
243     public static final String TIME_SHIFT_COMMAND_TYPE_PAUSE = "pause";
244     /**
245      * Time shift command type: resume.
246      *
247      * @see TvView#timeShiftResume()
248      */
249     public static final String TIME_SHIFT_COMMAND_TYPE_RESUME = "resume";
250     /**
251      * Time shift command type: seek to.
252      *
253      * @see TvView#timeShiftSeekTo(long)
254      */
255     public static final String TIME_SHIFT_COMMAND_TYPE_SEEK_TO = "seek_to";
256     /**
257      * Time shift command type: set playback params.
258      *
259      * @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
260      */
261     public static final String TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS = "set_playback_params";
262     /**
263      * Time shift command type: set time shift mode.
264      */
265     public static final String TIME_SHIFT_COMMAND_TYPE_SET_MODE = "set_mode";
266 
267     /**
268      * Time shift command parameter: program URI.
269      * <p>Type: android.net.Uri
270      *
271      * @see #TIME_SHIFT_COMMAND_TYPE_PLAY
272      */
273     public static final String COMMAND_PARAMETER_KEY_PROGRAM_URI = "command_program_uri";
274     /**
275      * Time shift command parameter: time position for time shifting, in milliseconds.
276      * <p>Type: long
277      *
278      * @see #TIME_SHIFT_COMMAND_TYPE_SEEK_TO
279      */
280     public static final String COMMAND_PARAMETER_KEY_TIME_POSITION = "command_time_position";
281     /**
282      * Time shift command parameter: playback params.
283      * <p>Type: android.media.PlaybackParams
284      *
285      * @see #TIME_SHIFT_COMMAND_TYPE_SET_PLAYBACK_PARAMS
286      */
287     public static final String COMMAND_PARAMETER_KEY_PLAYBACK_PARAMS = "command_playback_params";
288     /**
289      * Time shift command parameter: playback params.
290      * <p>Type: Integer. One of {@link TvInputManager#TIME_SHIFT_MODE_OFF},
291      * {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
292      * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
293      * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
294      *
295      * @see #TIME_SHIFT_COMMAND_TYPE_SET_MODE
296      */
297     public static final String COMMAND_PARAMETER_KEY_TIME_SHIFT_MODE = "command_time_shift_mode";
298 
299     private final Handler mServiceHandler = new ServiceHandler();
300     private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks =
301             new RemoteCallbackList<>();
302 
303     @Override
304     @Nullable
onBind(@onNull Intent intent)305     public final IBinder onBind(@NonNull Intent intent) {
306         ITvInteractiveAppService.Stub tvIAppServiceBinder = new ITvInteractiveAppService.Stub() {
307             @Override
308             public void registerCallback(ITvInteractiveAppServiceCallback cb) {
309                 if (cb != null) {
310                     mCallbacks.register(cb);
311                 }
312             }
313 
314             @Override
315             public void unregisterCallback(ITvInteractiveAppServiceCallback cb) {
316                 if (cb != null) {
317                     mCallbacks.unregister(cb);
318                 }
319             }
320 
321             @Override
322             public void createSession(InputChannel channel, ITvInteractiveAppSessionCallback cb,
323                     String iAppServiceId, int type) {
324                 if (cb == null) {
325                     return;
326                 }
327                 SomeArgs args = SomeArgs.obtain();
328                 args.arg1 = channel;
329                 args.arg2 = cb;
330                 args.arg3 = iAppServiceId;
331                 args.arg4 = type;
332                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
333                         .sendToTarget();
334             }
335 
336             @Override
337             public void registerAppLinkInfo(AppLinkInfo appLinkInfo) {
338                 onRegisterAppLinkInfo(appLinkInfo);
339             }
340 
341             @Override
342             public void unregisterAppLinkInfo(AppLinkInfo appLinkInfo) {
343                 onUnregisterAppLinkInfo(appLinkInfo);
344             }
345 
346             @Override
347             public void sendAppLinkCommand(Bundle command) {
348                 onAppLinkCommand(command);
349             }
350         };
351         return tvIAppServiceBinder;
352     }
353 
354     /**
355      * Called when a request to register an Android application link info record is received.
356      */
onRegisterAppLinkInfo(@onNull AppLinkInfo appLinkInfo)357     public void onRegisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
358     }
359 
360     /**
361      * Called when a request to unregister an Android application link info record is received.
362      */
onUnregisterAppLinkInfo(@onNull AppLinkInfo appLinkInfo)363     public void onUnregisterAppLinkInfo(@NonNull AppLinkInfo appLinkInfo) {
364     }
365 
366     /**
367      * Called when app link command is received.
368      *
369      * @see android.media.tv.interactive.TvInteractiveAppManager#sendAppLinkCommand(String, Bundle)
370      */
onAppLinkCommand(@onNull Bundle command)371     public void onAppLinkCommand(@NonNull Bundle command) {
372     }
373 
374 
375     /**
376      * Returns a concrete implementation of {@link Session}.
377      *
378      * <p>May return {@code null} if this TV Interactive App service fails to create a session for
379      * some reason.
380      *
381      * @param iAppServiceId The ID of the TV Interactive App associated with the session.
382      * @param type The type of the TV Interactive App associated with the session.
383      */
384     @Nullable
onCreateSession( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type)385     public abstract Session onCreateSession(
386             @NonNull String iAppServiceId,
387             @TvInteractiveAppServiceInfo.InteractiveAppType int type);
388 
389     /**
390      * Notifies the system when the state of the interactive app RTE has been changed.
391      *
392      * @param type the interactive app type
393      * @param state the current state of the service of the given type
394      * @param error the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
395      *              used when the state is not
396      *              {@link TvInteractiveAppManager#SERVICE_STATE_ERROR}.
397      */
notifyStateChanged( @vInteractiveAppServiceInfo.InteractiveAppType int type, @TvInteractiveAppManager.ServiceState int state, @TvInteractiveAppManager.ErrorCode int error)398     public final void notifyStateChanged(
399             @TvInteractiveAppServiceInfo.InteractiveAppType int type,
400             @TvInteractiveAppManager.ServiceState int state,
401             @TvInteractiveAppManager.ErrorCode int error) {
402         SomeArgs args = SomeArgs.obtain();
403         args.arg1 = type;
404         args.arg2 = state;
405         args.arg3 = error;
406         mServiceHandler
407                 .obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED, args).sendToTarget();
408     }
409 
410     /**
411      * Base class for derived classes to implement to provide a TV interactive app session.
412      *
413      * <p>A session is associated with a {@link TvInteractiveAppView} instance and handles
414      * corresponding communications. It also handles the communications with
415      * {@link android.media.tv.TvInputService.Session} if connected.
416      *
417      * @see TvInteractiveAppView#setTvView(TvView)
418      */
419     public abstract static class Session implements KeyEvent.Callback {
420         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
421 
422         private final Object mLock = new Object();
423         // @GuardedBy("mLock")
424         private ITvInteractiveAppSessionCallback mSessionCallback;
425         // @GuardedBy("mLock")
426         private final List<Runnable> mPendingActions = new ArrayList<>();
427 
428         private final Context mContext;
429         final Handler mHandler;
430         private final WindowManager mWindowManager;
431         private WindowManager.LayoutParams mWindowParams;
432         private Surface mSurface;
433         private FrameLayout mMediaViewContainer;
434         private View mMediaView;
435         private MediaViewCleanUpTask mMediaViewCleanUpTask;
436         private boolean mMediaViewEnabled;
437         private IBinder mWindowToken;
438         private Rect mMediaFrame;
439 
440         /**
441          * Creates a new Session.
442          *
443          * @param context The context of the application
444          */
Session(@onNull Context context)445         public Session(@NonNull Context context) {
446             mContext = context;
447             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
448             mHandler = new Handler(context.getMainLooper());
449         }
450 
451         /**
452          * Enables or disables the media view.
453          *
454          * <p>By default, the media view is disabled. Must be called explicitly after the
455          * session is created to enable the media view.
456          *
457          * <p>The TV Interactive App service can disable its media view when needed.
458          *
459          * @param enable {@code true} if you want to enable the media view. {@code false}
460          *            otherwise.
461          */
462         @CallSuper
setMediaViewEnabled(final boolean enable)463         public void setMediaViewEnabled(final boolean enable) {
464             mHandler.post(new Runnable() {
465                 @Override
466                 public void run() {
467                     if (enable == mMediaViewEnabled) {
468                         return;
469                     }
470                     mMediaViewEnabled = enable;
471                     if (enable) {
472                         if (mWindowToken != null) {
473                             createMediaView(mWindowToken, mMediaFrame);
474                         }
475                     } else {
476                         removeMediaView(false);
477                     }
478                 }
479             });
480         }
481 
482         /**
483          * Returns {@code true} if media view is enabled, {@code false} otherwise.
484          *
485          * @see #setMediaViewEnabled(boolean)
486          */
isMediaViewEnabled()487         public boolean isMediaViewEnabled() {
488             return mMediaViewEnabled;
489         }
490 
491         /**
492          * Starts TvInteractiveAppService session.
493          */
onStartInteractiveApp()494         public void onStartInteractiveApp() {
495         }
496 
497         /**
498          * Stops TvInteractiveAppService session.
499          */
onStopInteractiveApp()500         public void onStopInteractiveApp() {
501         }
502 
503         /**
504          * Resets TvInteractiveAppService session.
505          */
onResetInteractiveApp()506         public void onResetInteractiveApp() {
507         }
508 
509         /**
510          * Creates broadcast-independent(BI) interactive application.
511          *
512          * <p>The implementation should call {@link #notifyBiInteractiveAppCreated(Uri, String)},
513          * no matter if it's created successfully or not.
514          *
515          * @see #notifyBiInteractiveAppCreated(Uri, String)
516          * @see #onDestroyBiInteractiveAppRequest(String)
517          */
onCreateBiInteractiveAppRequest( @onNull Uri biIAppUri, @Nullable Bundle params)518         public void onCreateBiInteractiveAppRequest(
519                 @NonNull Uri biIAppUri, @Nullable Bundle params) {
520         }
521 
522 
523         /**
524          * Destroys broadcast-independent(BI) interactive application.
525          *
526          * @param biIAppId the BI interactive app ID from
527          *                 {@link #onCreateBiInteractiveAppRequest(Uri, Bundle)}
528          *
529          * @see #onCreateBiInteractiveAppRequest(Uri, Bundle)
530          */
onDestroyBiInteractiveAppRequest(@onNull String biIAppId)531         public void onDestroyBiInteractiveAppRequest(@NonNull String biIAppId) {
532         }
533 
534         /**
535          * To toggle Digital Teletext Application if there is one in AIT app list.
536          * @param enable {@code true} to enable teletext app; {@code false} otherwise.
537          */
onSetTeletextAppEnabled(boolean enable)538         public void onSetTeletextAppEnabled(boolean enable) {
539         }
540 
541         /**
542          * Receives current video bounds.
543          *
544          * @param bounds the rectangle area for rendering the current video.
545          */
onCurrentVideoBounds(@onNull Rect bounds)546         public void onCurrentVideoBounds(@NonNull Rect bounds) {
547         }
548 
549         /**
550          * Receives current channel URI.
551          */
onCurrentChannelUri(@ullable Uri channelUri)552         public void onCurrentChannelUri(@Nullable Uri channelUri) {
553         }
554 
555         /**
556          * Receives logical channel number (LCN) of current channel.
557          */
onCurrentChannelLcn(int lcn)558         public void onCurrentChannelLcn(int lcn) {
559         }
560 
561         /**
562          * Receives current stream volume.
563          *
564          * @param volume a volume value between {@code 0.0f} and {@code 1.0f}, inclusive.
565          */
onStreamVolume(float volume)566         public void onStreamVolume(float volume) {
567         }
568 
569         /**
570          * Receives track list.
571          */
onTrackInfoList(@onNull List<TvTrackInfo> tracks)572         public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
573         }
574 
575         /**
576          * Receives current TV input ID.
577          */
onCurrentTvInputId(@ullable String inputId)578         public void onCurrentTvInputId(@Nullable String inputId) {
579         }
580 
581         /**
582          * Receives current time shift mode.
583          *
584          * @param mode The current time shift mode. The value is one of the following:
585          * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
586          * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
587          * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
588          */
onTimeShiftMode(@ndroid.media.tv.TvInputManager.TimeShiftMode int mode)589         public void onTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
590         }
591 
592         /**
593          * Receives available playback speeds.
594          *
595          * @param speeds An ordered array of playback speeds, expressed as values relative to the
596          *               normal playback speed (1.0), at which the current content can be played as
597          *               a time-shifted broadcast. This is an empty array if the supported playback
598          *               speeds are unknown or the video/broadcast is not in time shift mode. If
599          *               currently in time shift mode, this array will normally include at least
600          *               the values 1.0 (normal speed) and 0.0 (paused).
601          */
onAvailableSpeeds(@onNull float[] speeds)602         public void onAvailableSpeeds(@NonNull float[] speeds) {
603         }
604 
605         /**
606          * Receives the requested {@link android.media.tv.TvRecordingInfo}.
607          *
608          * @see #requestTvRecordingInfo(String)
609          * @param recordingInfo The requested recording info. {@code null} if no recording found.
610          */
onTvRecordingInfo(@ullable TvRecordingInfo recordingInfo)611         public void onTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
612         }
613 
614         /**
615          * Receives requested recording info list.
616          *
617          * @see #requestTvRecordingInfoList(int)
618          * @param recordingInfoList The list of recording info requested. Returns an empty list if
619          *                          no matching recording info found.
620          */
onTvRecordingInfoList(@onNull List<TvRecordingInfo> recordingInfoList)621         public void onTvRecordingInfoList(@NonNull List<TvRecordingInfo> recordingInfoList) {}
622 
623         /**
624          * This is called when a recording has been started.
625          *
626          * <p>When a scheduled recording is started, this is also called, and the request ID in this
627          * case is {@code null}.
628          *
629          * @param recordingId The ID of the recording started. The TV app should provide and
630          *                    maintain this ID to identify the recording in the future.
631          * @param requestId The ID of the request when
632          *                  {@link #requestStartRecording(String, Uri)} is called.
633          *                  {@code null} if the recording is not triggered by a
634          *                  {@link #requestStartRecording(String, Uri)} request.
635          *                  This ID should be created by the {@link TvInteractiveAppService} and
636          *                  can be any string.
637          * @see #onRecordingStopped(String)
638          */
onRecordingStarted(@onNull String recordingId, @Nullable String requestId)639         public void onRecordingStarted(@NonNull String recordingId, @Nullable String requestId) {
640         }
641 
642         /**
643          * This is called when the recording has been stopped.
644          *
645          * @param recordingId The ID of the recording stopped. This ID is created and maintained by
646          *                    the TV app when the recording was started.
647          * @see #onRecordingStarted(String, String)
648          */
onRecordingStopped(@onNull String recordingId)649         public void onRecordingStopped(@NonNull String recordingId) {
650         }
651 
652         /**
653          * This is called when an error occurred while establishing a connection to the recording
654          * session for the corresponding TV input.
655          *
656          * @param recordingId The ID of the related recording which is sent via
657          *                    {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
658          * @param inputId The ID of the TV input bound to the current TvRecordingClient.
659          * @see android.media.tv.TvRecordingClient.RecordingCallback#onConnectionFailed(String)
660          */
onRecordingConnectionFailed( @onNull String recordingId, @NonNull String inputId)661         public void onRecordingConnectionFailed(
662                 @NonNull String recordingId, @NonNull String inputId) {
663         }
664 
665         /**
666          * This is called when the connection to the current recording session is lost.
667          *
668          * @param recordingId The ID of the related recording which is sent via
669          *                    {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
670          * @param inputId The ID of the TV input bound to the current TvRecordingClient.
671          * @see android.media.tv.TvRecordingClient.RecordingCallback#onDisconnected(String)
672          */
onRecordingDisconnected(@onNull String recordingId, @NonNull String inputId)673         public void onRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) {
674         }
675 
676         /**
677          * This is called when the recording session has been tuned to the given channel and is
678          * ready to start recording.
679          *
680          * @param recordingId The ID of the related recording which is sent via
681          *                    {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
682          * @param channelUri The URI of the tuned channel.
683          * @see android.media.tv.TvRecordingClient.RecordingCallback#onTuned(Uri)
684          */
onRecordingTuned(@onNull String recordingId, @NonNull Uri channelUri)685         public void onRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) {
686         }
687 
688         /**
689          * This is called when an issue has occurred. It may be called at any time after the current
690          * recording session is created until it is released.
691          *
692          * @param recordingId The ID of the related recording which is sent via
693          *                    {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
694          * @param err The error code. Should be one of the following.
695          * <ul>
696          * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
697          * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE}
698          * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
699          * </ul>
700          * @see android.media.tv.TvRecordingClient.RecordingCallback#onError(int)
701          */
onRecordingError( @onNull String recordingId, @TvInputManager.RecordingError int err)702         public void onRecordingError(
703                 @NonNull String recordingId, @TvInputManager.RecordingError int err) {
704         }
705 
706         /**
707          * This is called when the recording has been scheduled.
708          *
709          * @param recordingId The ID assigned to this recording by the app. It can be used to send
710          *                    recording related requests such as
711          *                    {@link #requestStopRecording(String)}.
712          * @param requestId The ID of the request when
713          *                  {@link #requestScheduleRecording}  is called.
714          *                  {@code null} if the recording is not triggered by a request.
715          *                  This ID should be created by the {@link TvInteractiveAppService} and
716          *                  can be any string.
717          */
onRecordingScheduled(@onNull String recordingId, @Nullable String requestId)718         public void onRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) {
719         }
720 
721         /**
722          * Receives signing result.
723          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
724          *        {@link Session#requestSigning(String, String, String, byte[])}
725          * @param result the signed result.
726          *
727          * @see #requestSigning(String, String, String, byte[])
728          */
onSigningResult(@onNull String signingId, @NonNull byte[] result)729         public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
730         }
731 
732         /**
733          * Called when the application sends information of an error.
734          *
735          * @param errMsg the message of the error.
736          * @param params additional parameters of the error. For example, the signingId of {@link
737          *     TvInteractiveAppCallback#onRequestSigning(String, String, String, String, byte[])}
738          *     can be included to identify the related signing request, and the method name
739          *     "onRequestSigning" can also be added to the params.
740          *
741          * @see TvInteractiveAppView#ERROR_KEY_METHOD_NAME
742          */
onError(@onNull String errMsg, @NonNull Bundle params)743         public void onError(@NonNull String errMsg, @NonNull Bundle params) {
744         }
745 
746         /**
747          * Called when the time shift {@link android.media.PlaybackParams} is set or changed.
748          *
749          * @param params The new {@link PlaybackParams} that was set or changed.
750          * @see TvView#timeShiftSetPlaybackParams(PlaybackParams)
751          */
onTimeShiftPlaybackParams(@onNull PlaybackParams params)752         public void onTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
753         }
754 
755         /**
756          * Called when time shift status is changed.
757          *
758          * @see TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)
759          * @see android.media.tv.TvInputService.Session#notifyTimeShiftStatusChanged(int)
760          * @param inputId The ID of the input for which the time shift status has changed.
761          * @param status The status of which the input has changed to. Should be one of the
762          *               following.
763          *               <ul>
764          *                  <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}
765          *                  <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
766          *                  <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
767          *                  <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
768          *               </ul>
769          */
onTimeShiftStatusChanged( @onNull String inputId, @TvInputManager.TimeShiftStatus int status)770         public void onTimeShiftStatusChanged(
771                 @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {}
772 
773         /**
774          * Called when time shift start position is changed.
775          *
776          * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged(String, long)
777          * @param inputId The ID of the input for which the time shift start position has changed.
778          * @param timeMs The start position for time shifting, in milliseconds since the epoch.
779          */
onTimeShiftStartPositionChanged(@onNull String inputId, long timeMs)780         public void onTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
781         }
782 
783         /**
784          * Called when time shift current position is changed.
785          *
786          * @see TvView.TimeShiftPositionCallback#onTimeShiftCurrentPositionChanged(String, long)
787          * @param inputId The ID of the input for which the time shift current position has changed.
788          * @param timeMs The current position for time shifting, in milliseconds since the epoch.
789          */
onTimeShiftCurrentPositionChanged(@onNull String inputId, long timeMs)790         public void onTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
791         }
792 
793         /**
794          * Called when the application sets the surface.
795          *
796          * <p>The TV Interactive App service should render interactive app UI onto the given
797          * surface. When called with {@code null}, the Interactive App service should immediately
798          * free any references to the currently set surface and stop using it.
799          *
800          * @param surface The surface to be used for interactive app UI rendering. Can be
801          *                {@code null}.
802          * @return {@code true} if the surface was set successfully, {@code false} otherwise.
803          */
onSetSurface(@ullable Surface surface)804         public abstract boolean onSetSurface(@Nullable Surface surface);
805 
806         /**
807          * Called after any structural changes (format or size) have been made to the surface passed
808          * in {@link #onSetSurface}. This method is always called at least once, after
809          * {@link #onSetSurface} is called with non-null surface.
810          *
811          * @param format The new {@link PixelFormat} of the surface.
812          * @param width The new width of the surface.
813          * @param height The new height of the surface.
814          */
onSurfaceChanged(@ixelFormat.Format int format, int width, int height)815         public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
816         }
817 
818         /**
819          * Called when the size of the media view is changed by the application.
820          *
821          * <p>This is always called at least once when the session is created regardless of whether
822          * the media view is enabled or not. The media view container size is the same as the
823          * containing {@link TvInteractiveAppView}. Note that the size of the underlying surface can
824          * be different if the surface was changed by calling {@link #layoutSurface}.
825          *
826          * @param width The width of the media view, in pixels.
827          * @param height The height of the media view, in pixels.
828          */
onMediaViewSizeChanged(@x int width, @Px int height)829         public void onMediaViewSizeChanged(@Px int width, @Px int height) {
830         }
831 
832         /**
833          * Called when the application requests to create an media view. Each session
834          * implementation can override this method and return its own view.
835          *
836          * @return a view attached to the media window
837          */
838         @Nullable
onCreateMediaView()839         public View onCreateMediaView() {
840             return null;
841         }
842 
843         /**
844          * Releases TvInteractiveAppService session.
845          */
onRelease()846         public abstract void onRelease();
847 
848         /**
849          * Called when the corresponding TV input tuned to a channel.
850          *
851          * @param channelUri The tuned channel URI.
852          */
onTuned(@onNull Uri channelUri)853         public void onTuned(@NonNull Uri channelUri) {
854         }
855 
856         /**
857          * Called when the corresponding TV input selected to a track.
858          */
onTrackSelected(@vTrackInfo.Type int type, @NonNull String trackId)859         public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) {
860         }
861 
862         /**
863          * Called when the tracks are changed.
864          */
onTracksChanged(@onNull List<TvTrackInfo> tracks)865         public void onTracksChanged(@NonNull List<TvTrackInfo> tracks) {
866         }
867 
868         /**
869          * Called when video is available.
870          */
onVideoAvailable()871         public void onVideoAvailable() {
872         }
873 
874         /**
875          * Called when video is unavailable.
876          */
onVideoUnavailable(@vInputManager.VideoUnavailableReason int reason)877         public void onVideoUnavailable(@TvInputManager.VideoUnavailableReason int reason) {
878         }
879 
880         /**
881          * Called when content is allowed.
882          */
onContentAllowed()883         public void onContentAllowed() {
884         }
885 
886         /**
887          * Called when content is blocked.
888          */
onContentBlocked(@onNull TvContentRating rating)889         public void onContentBlocked(@NonNull TvContentRating rating) {
890         }
891 
892         /**
893          * Called when signal strength is changed.
894          */
onSignalStrength(@vInputManager.SignalStrength int strength)895         public void onSignalStrength(@TvInputManager.SignalStrength int strength) {
896         }
897 
898         /**
899          * Called when a broadcast info response is received.
900          */
onBroadcastInfoResponse(@onNull BroadcastInfoResponse response)901         public void onBroadcastInfoResponse(@NonNull BroadcastInfoResponse response) {
902         }
903 
904         /**
905          * Called when an advertisement response is received.
906          */
onAdResponse(@onNull AdResponse response)907         public void onAdResponse(@NonNull AdResponse response) {
908         }
909 
910         /**
911          * Called when an advertisement buffer is consumed.
912          *
913          * @param buffer The {@link AdBuffer} that was consumed.
914          */
onAdBufferConsumed(@onNull AdBuffer buffer)915         public void onAdBufferConsumed(@NonNull AdBuffer buffer) {
916         }
917 
918         /**
919          * Called when a TV message is received
920          *
921          * @param type The type of message received, such as
922          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
923          * @param data The raw data of the message. The bundle keys are:
924          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
925          *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
926          *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
927          *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
928          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
929          *             how to parse this data.
930          */
onTvMessage(@vInputManager.TvMessageType int type, @NonNull Bundle data)931         public void onTvMessage(@TvInputManager.TvMessageType int type,
932                 @NonNull Bundle data) {
933         }
934 
935         @Override
onKeyDown(int keyCode, @NonNull KeyEvent event)936         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
937             return false;
938         }
939 
940         @Override
onKeyLongPress(int keyCode, @NonNull KeyEvent event)941         public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
942             return false;
943         }
944 
945         @Override
onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event)946         public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
947             return false;
948         }
949 
950         @Override
onKeyUp(int keyCode, @NonNull KeyEvent event)951         public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
952             return false;
953         }
954 
955         /**
956          * Implement this method to handle touch screen motion events on the current session.
957          *
958          * @param event The motion event being received.
959          * @return If you handled the event, return {@code true}. If you want to allow the event to
960          *         be handled by the next receiver, return {@code false}.
961          * @see View#onTouchEvent
962          */
onTouchEvent(@onNull MotionEvent event)963         public boolean onTouchEvent(@NonNull MotionEvent event) {
964             return false;
965         }
966 
967         /**
968          * Implement this method to handle trackball events on the current session.
969          *
970          * @param event The motion event being received.
971          * @return If you handled the event, return {@code true}. If you want to allow the event to
972          *         be handled by the next receiver, return {@code false}.
973          * @see View#onTrackballEvent
974          */
onTrackballEvent(@onNull MotionEvent event)975         public boolean onTrackballEvent(@NonNull MotionEvent event) {
976             return false;
977         }
978 
979         /**
980          * Implement this method to handle generic motion events on the current session.
981          *
982          * @param event The motion event being received.
983          * @return If you handled the event, return {@code true}. If you want to allow the event to
984          *         be handled by the next receiver, return {@code false}.
985          * @see View#onGenericMotionEvent
986          */
onGenericMotionEvent(@onNull MotionEvent event)987         public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
988             return false;
989         }
990 
991         /**
992          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
993          * is relative to the overlay view that sits on top of this surface.
994          *
995          * @param left Left position in pixels, relative to the overlay view.
996          * @param top Top position in pixels, relative to the overlay view.
997          * @param right Right position in pixels, relative to the overlay view.
998          * @param bottom Bottom position in pixels, relative to the overlay view.
999          */
1000         @CallSuper
layoutSurface(final int left, final int top, final int right, final int bottom)1001         public void layoutSurface(final int left, final int top, final int right,
1002                 final int bottom) {
1003             if (left > right || top > bottom) {
1004                 throw new IllegalArgumentException("Invalid parameter");
1005             }
1006             executeOrPostRunnableOnMainThread(new Runnable() {
1007                 @MainThread
1008                 @Override
1009                 public void run() {
1010                     try {
1011                         if (DEBUG) {
1012                             Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
1013                                     + ", r=" + right + ", b=" + bottom + ",)");
1014                         }
1015                         if (mSessionCallback != null) {
1016                             mSessionCallback.onLayoutSurface(left, top, right, bottom);
1017                         }
1018                     } catch (RemoteException e) {
1019                         Log.w(TAG, "error in layoutSurface", e);
1020                     }
1021                 }
1022             });
1023         }
1024 
1025         /**
1026          * Requests broadcast related information from the related TV input.
1027          * @param request the request for broadcast info
1028          */
1029         @CallSuper
requestBroadcastInfo(@onNull final BroadcastInfoRequest request)1030         public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
1031             executeOrPostRunnableOnMainThread(new Runnable() {
1032                 @MainThread
1033                 @Override
1034                 public void run() {
1035                     try {
1036                         if (DEBUG) {
1037                             Log.d(TAG, "requestBroadcastInfo (requestId="
1038                                     + request.getRequestId() + ")");
1039                         }
1040                         if (mSessionCallback != null) {
1041                             mSessionCallback.onBroadcastInfoRequest(request);
1042                         }
1043                     } catch (RemoteException e) {
1044                         Log.w(TAG, "error in requestBroadcastInfo", e);
1045                     }
1046                 }
1047             });
1048         }
1049 
1050         /**
1051          * Remove broadcast information request from the related TV input.
1052          * @param requestId the ID of the request
1053          */
1054         @CallSuper
removeBroadcastInfo(final int requestId)1055         public void removeBroadcastInfo(final int requestId) {
1056             executeOrPostRunnableOnMainThread(new Runnable() {
1057                 @MainThread
1058                 @Override
1059                 public void run() {
1060                     try {
1061                         if (DEBUG) {
1062                             Log.d(TAG, "removeBroadcastInfo (requestId="
1063                                     + requestId + ")");
1064                         }
1065                         if (mSessionCallback != null) {
1066                             mSessionCallback.onRemoveBroadcastInfo(requestId);
1067                         }
1068                     } catch (RemoteException e) {
1069                         Log.w(TAG, "error in removeBroadcastInfo", e);
1070                     }
1071                 }
1072             });
1073         }
1074 
1075         /**
1076          * Sends a specific playback command to be processed by the related TV input.
1077          *
1078          * @param cmdType type of the specific command
1079          * @param parameters parameters of the specific command
1080          */
1081         @CallSuper
sendPlaybackCommandRequest( @laybackCommandType @onNull String cmdType, @Nullable Bundle parameters)1082         public void sendPlaybackCommandRequest(
1083                 @PlaybackCommandType @NonNull String cmdType, @Nullable Bundle parameters) {
1084             executeOrPostRunnableOnMainThread(new Runnable() {
1085                 @MainThread
1086                 @Override
1087                 public void run() {
1088                     try {
1089                         if (DEBUG) {
1090                             Log.d(TAG, "requestCommand (cmdType=" + cmdType + ", parameters="
1091                                     + parameters.toString() + ")");
1092                         }
1093                         if (mSessionCallback != null) {
1094                             mSessionCallback.onCommandRequest(cmdType, parameters);
1095                         }
1096                     } catch (RemoteException e) {
1097                         Log.w(TAG, "error in requestCommand", e);
1098                     }
1099                 }
1100             });
1101         }
1102 
1103         /**
1104          * Sends a specific time shift command to be processed by the related TV input.
1105          *
1106          * @param cmdType type of the specific command
1107          * @param parameters parameters of the specific command
1108          */
1109         @CallSuper
sendTimeShiftCommandRequest( @imeShiftCommandType @onNull String cmdType, @Nullable Bundle parameters)1110         public void sendTimeShiftCommandRequest(
1111                 @TimeShiftCommandType @NonNull String cmdType, @Nullable Bundle parameters) {
1112             executeOrPostRunnableOnMainThread(new Runnable() {
1113                 @MainThread
1114                 @Override
1115                 public void run() {
1116                     try {
1117                         if (DEBUG) {
1118                             Log.d(TAG, "requestTimeShiftCommand (cmdType=" + cmdType
1119                                     + ", parameters=" + parameters.toString() + ")");
1120                         }
1121                         if (mSessionCallback != null) {
1122                             mSessionCallback.onTimeShiftCommandRequest(cmdType, parameters);
1123                         }
1124                     } catch (RemoteException e) {
1125                         Log.w(TAG, "error in requestTimeShiftCommand", e);
1126                     }
1127                 }
1128             });
1129         }
1130 
1131         /**
1132          * Sets broadcast video bounds.
1133          */
1134         @CallSuper
setVideoBounds(@onNull Rect rect)1135         public void setVideoBounds(@NonNull Rect rect) {
1136             executeOrPostRunnableOnMainThread(new Runnable() {
1137                 @MainThread
1138                 @Override
1139                 public void run() {
1140                     try {
1141                         if (DEBUG) {
1142                             Log.d(TAG, "setVideoBounds (rect=" + rect + ")");
1143                         }
1144                         if (mSessionCallback != null) {
1145                             mSessionCallback.onSetVideoBounds(rect);
1146                         }
1147                     } catch (RemoteException e) {
1148                         Log.w(TAG, "error in setVideoBounds", e);
1149                     }
1150                 }
1151             });
1152         }
1153 
1154         /**
1155          * Requests the bounds of the current video.
1156          */
1157         @CallSuper
requestCurrentVideoBounds()1158         public void requestCurrentVideoBounds() {
1159             executeOrPostRunnableOnMainThread(new Runnable() {
1160                 @MainThread
1161                 @Override
1162                 public void run() {
1163                     try {
1164                         if (DEBUG) {
1165                             Log.d(TAG, "requestCurrentVideoBounds");
1166                         }
1167                         if (mSessionCallback != null) {
1168                             mSessionCallback.onRequestCurrentVideoBounds();
1169                         }
1170                     } catch (RemoteException e) {
1171                         Log.w(TAG, "error in requestCurrentVideoBounds", e);
1172                     }
1173                 }
1174             });
1175         }
1176 
1177         /**
1178          * Requests the URI of the current channel.
1179          */
1180         @CallSuper
requestCurrentChannelUri()1181         public void requestCurrentChannelUri() {
1182             executeOrPostRunnableOnMainThread(new Runnable() {
1183                 @MainThread
1184                 @Override
1185                 public void run() {
1186                     try {
1187                         if (DEBUG) {
1188                             Log.d(TAG, "requestCurrentChannelUri");
1189                         }
1190                         if (mSessionCallback != null) {
1191                             mSessionCallback.onRequestCurrentChannelUri();
1192                         }
1193                     } catch (RemoteException e) {
1194                         Log.w(TAG, "error in requestCurrentChannelUri", e);
1195                     }
1196                 }
1197             });
1198         }
1199 
1200         /**
1201          * Requests the logic channel number (LCN) of the current channel.
1202          */
1203         @CallSuper
requestCurrentChannelLcn()1204         public void requestCurrentChannelLcn() {
1205             executeOrPostRunnableOnMainThread(new Runnable() {
1206                 @MainThread
1207                 @Override
1208                 public void run() {
1209                     try {
1210                         if (DEBUG) {
1211                             Log.d(TAG, "requestCurrentChannelLcn");
1212                         }
1213                         if (mSessionCallback != null) {
1214                             mSessionCallback.onRequestCurrentChannelLcn();
1215                         }
1216                     } catch (RemoteException e) {
1217                         Log.w(TAG, "error in requestCurrentChannelLcn", e);
1218                     }
1219                 }
1220             });
1221         }
1222 
1223         /**
1224          * Requests stream volume.
1225          */
1226         @CallSuper
requestStreamVolume()1227         public void requestStreamVolume() {
1228             executeOrPostRunnableOnMainThread(new Runnable() {
1229                 @MainThread
1230                 @Override
1231                 public void run() {
1232                     try {
1233                         if (DEBUG) {
1234                             Log.d(TAG, "requestStreamVolume");
1235                         }
1236                         if (mSessionCallback != null) {
1237                             mSessionCallback.onRequestStreamVolume();
1238                         }
1239                     } catch (RemoteException e) {
1240                         Log.w(TAG, "error in requestStreamVolume", e);
1241                     }
1242                 }
1243             });
1244         }
1245 
1246         /**
1247          * Requests the list of {@link TvTrackInfo}.
1248          */
1249         @CallSuper
requestTrackInfoList()1250         public void requestTrackInfoList() {
1251             executeOrPostRunnableOnMainThread(new Runnable() {
1252                 @MainThread
1253                 @Override
1254                 public void run() {
1255                     try {
1256                         if (DEBUG) {
1257                             Log.d(TAG, "requestTrackInfoList");
1258                         }
1259                         if (mSessionCallback != null) {
1260                             mSessionCallback.onRequestTrackInfoList();
1261                         }
1262                     } catch (RemoteException e) {
1263                         Log.w(TAG, "error in requestTrackInfoList", e);
1264                     }
1265                 }
1266             });
1267         }
1268 
1269         /**
1270          * Requests current TV input ID.
1271          *
1272          * @see android.media.tv.TvInputInfo
1273          */
1274         @CallSuper
requestCurrentTvInputId()1275         public void requestCurrentTvInputId() {
1276             executeOrPostRunnableOnMainThread(new Runnable() {
1277                 @MainThread
1278                 @Override
1279                 public void run() {
1280                     try {
1281                         if (DEBUG) {
1282                             Log.d(TAG, "requestCurrentTvInputId");
1283                         }
1284                         if (mSessionCallback != null) {
1285                             mSessionCallback.onRequestCurrentTvInputId();
1286                         }
1287                     } catch (RemoteException e) {
1288                         Log.w(TAG, "error in requestCurrentTvInputId", e);
1289                     }
1290                 }
1291             });
1292         }
1293 
1294         /**
1295          * Requests time shift mode.
1296          */
1297         @CallSuper
requestTimeShiftMode()1298         public void requestTimeShiftMode() {
1299             executeOrPostRunnableOnMainThread(new Runnable() {
1300                 @MainThread
1301                 @Override
1302                 public void run() {
1303                     try {
1304                         if (DEBUG) {
1305                             Log.d(TAG, "requestTimeShiftMode");
1306                         }
1307                         if (mSessionCallback != null) {
1308                             mSessionCallback.onRequestTimeShiftMode();
1309                         }
1310                     } catch (RemoteException e) {
1311                         Log.w(TAG, "error in requestTimeShiftMode", e);
1312                     }
1313                 }
1314             });
1315         }
1316 
1317         /**
1318          * Requests available speeds for time shift.
1319          */
1320         @CallSuper
requestAvailableSpeeds()1321         public void requestAvailableSpeeds() {
1322             executeOrPostRunnableOnMainThread(new Runnable() {
1323                 @MainThread
1324                 @Override
1325                 public void run() {
1326                     try {
1327                         if (DEBUG) {
1328                             Log.d(TAG, "requestAvailableSpeeds");
1329                         }
1330                         if (mSessionCallback != null) {
1331                             mSessionCallback.onRequestAvailableSpeeds();
1332                         }
1333                     } catch (RemoteException e) {
1334                         Log.w(TAG, "error in requestAvailableSpeeds", e);
1335                     }
1336                 }
1337             });
1338         }
1339 
1340         /**
1341          * Requests starting of recording
1342          *
1343          * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
1344          * call {@link android.media.tv.TvRecordingClient#startRecording(Uri)} with the provided
1345          * {@code programUri}.
1346          * A non-null {@code programUri} implies the started recording should be of that specific
1347          * program, whereas null {@code programUri} does not impose such a requirement and the
1348          * recording can span across multiple TV programs.
1349          *
1350          * @param requestId The ID of this request which is used to match the corresponding
1351          *                  response. The request ID in
1352          *                  {@link #onRecordingStarted(String, String)} for this request is the
1353          *                  same as the ID sent here. This should be defined by the
1354          *                  {@link TvInteractiveAppService} and can be any string.
1355          *                  Should this API be called with the same requestId twice, both
1356          *                  requests should be handled regardless by the TV application.
1357          * @param programUri The URI for the TV program to record, built by
1358          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
1359          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
1360          */
1361         @CallSuper
requestStartRecording(@onNull String requestId, @Nullable Uri programUri)1362         public void requestStartRecording(@NonNull String requestId, @Nullable Uri programUri) {
1363             executeOrPostRunnableOnMainThread(() -> {
1364                 try {
1365                     if (DEBUG) {
1366                         Log.d(TAG, "requestStartRecording");
1367                     }
1368                     if (mSessionCallback != null) {
1369                         mSessionCallback.onRequestStartRecording(requestId, programUri);
1370                     }
1371                 } catch (RemoteException e) {
1372                     Log.w(TAG, "error in requestStartRecording", e);
1373                 }
1374             });
1375         }
1376 
1377         /**
1378          * Requests the recording associated with the recordingId to stop.
1379          *
1380          * <p> This is used to request the associated {@link android.media.tv.TvRecordingClient} to
1381          * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
1382          *
1383          * @param recordingId The ID of the recording to stop. This is provided by the TV app in
1384          *                    {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
1385          * @see android.media.tv.TvRecordingClient#stopRecording()
1386          */
1387         @CallSuper
requestStopRecording(@onNull String recordingId)1388         public void requestStopRecording(@NonNull String recordingId) {
1389             executeOrPostRunnableOnMainThread(() -> {
1390                 try {
1391                     if (DEBUG) {
1392                         Log.d(TAG, "requestStopRecording");
1393                     }
1394                     if (mSessionCallback != null) {
1395                         mSessionCallback.onRequestStopRecording(recordingId);
1396                     }
1397                 } catch (RemoteException e) {
1398                     Log.w(TAG, "error in requestStopRecording", e);
1399                 }
1400             });
1401         }
1402 
1403         /**
1404          * Requests scheduling of a recording.
1405          *
1406          * @param requestId The ID of this request which is used to match the corresponding
1407          *                  response. The request ID in
1408          *                  {@link #onRecordingScheduled(String, String)} for this request is the
1409          *                  same as the ID sent here. This should be defined by the
1410          *                  {@link TvInteractiveAppService} and can be any string.
1411          *                  Should this API be called with the same requestId twice, both requests
1412          *                  should be handled regardless by the TV application.
1413          * @param inputId The ID of the TV input for the given channel.
1414          * @param channelUri The URI of a channel to be recorded.
1415          * @param programUri The URI of the TV program to be recorded.
1416          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
1417          *            name, i.e. prefixed with a package name you own, so that different developers
1418          *            will not create conflicting keys.
1419          * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
1420          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
1421          */
1422         @CallSuper
requestScheduleRecording(@onNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params)1423         public void requestScheduleRecording(@NonNull String requestId, @NonNull String inputId,
1424                 @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params) {
1425             executeOrPostRunnableOnMainThread(() -> {
1426                 try {
1427                     if (DEBUG) {
1428                         Log.d(TAG, "requestScheduleRecording");
1429                     }
1430                     if (mSessionCallback != null) {
1431                         mSessionCallback.onRequestScheduleRecording(
1432                                 requestId, inputId, channelUri, programUri, params);
1433                     }
1434                 } catch (RemoteException e) {
1435                     Log.w(TAG, "error in requestScheduleRecording", e);
1436                 }
1437             });
1438         }
1439 
1440         /**
1441          * Requests scheduling of a recording.
1442          *
1443          * @param requestId The ID of this request which is used to match the corresponding
1444          *                  response. The request ID in
1445          *                  {@link #onRecordingScheduled(String, String)} for this request is the
1446          *                  same as the ID sent here. This should be defined by the
1447          *                  {@link TvInteractiveAppService} and can be any string. Should this API
1448          *                  be called with the same requestId twice, both requests should be handled
1449          *                  regardless by the TV application.
1450          * @param inputId The ID of the TV input for the given channel.
1451          * @param channelUri The URI of a channel to be recorded.
1452          * @param startTime The start time of the recording in milliseconds since epoch.
1453          * @param duration The duration of the recording in milliseconds.
1454          * @param repeatDays The repeated days. 0 if not repeated.
1455          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
1456          *            name, i.e. prefixed with a package name you own, so that different developers
1457          *            will not create conflicting keys.
1458          * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
1459          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
1460          */
1461         @CallSuper
requestScheduleRecording(@onNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, int repeatDays, @NonNull Bundle params)1462         public void requestScheduleRecording(@NonNull String requestId, @NonNull String inputId,
1463                 @NonNull Uri channelUri, long startTime, long duration, int repeatDays,
1464                 @NonNull Bundle params) {
1465             executeOrPostRunnableOnMainThread(() -> {
1466                 try {
1467                     if (DEBUG) {
1468                         Log.d(TAG, "requestScheduleRecording");
1469                     }
1470                     if (mSessionCallback != null) {
1471                         mSessionCallback.onRequestScheduleRecording2(requestId, inputId, channelUri,
1472                                 startTime, duration, repeatDays, params);
1473                     }
1474                 } catch (RemoteException e) {
1475                     Log.w(TAG, "error in requestScheduleRecording", e);
1476                 }
1477             });
1478         }
1479 
1480         /**
1481          * Sets the recording info for the specified recording
1482          *
1483          * @param recordingId The ID of the recording to set the info for. This is provided by the
1484          *     TV app in {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
1485          * @param recordingInfo The {@link TvRecordingInfo} to set to the recording.
1486          */
1487         @CallSuper
setTvRecordingInfo( @onNull String recordingId, @NonNull TvRecordingInfo recordingInfo)1488         public void setTvRecordingInfo(
1489                 @NonNull String recordingId, @NonNull TvRecordingInfo recordingInfo) {
1490             executeOrPostRunnableOnMainThread(() -> {
1491                 try {
1492                     if (DEBUG) {
1493                         Log.d(TAG, "setTvRecordingInfo");
1494                     }
1495                     if (mSessionCallback != null) {
1496                         mSessionCallback.onSetTvRecordingInfo(recordingId, recordingInfo);
1497                     }
1498                 } catch (RemoteException e) {
1499                     Log.w(TAG, "error in setTvRecordingInfo", e);
1500                 }
1501             });
1502         }
1503 
1504         /**
1505          * Gets the recording info for the specified recording
1506          * @param recordingId The ID of the recording to set the info for. This is provided by the
1507          *                    TV app in
1508          *                    {@link TvInteractiveAppView#notifyRecordingStarted(String, String)}
1509          */
1510         @CallSuper
requestTvRecordingInfo(@onNull String recordingId)1511         public void requestTvRecordingInfo(@NonNull String recordingId) {
1512             executeOrPostRunnableOnMainThread(() -> {
1513                 try {
1514                     if (DEBUG) {
1515                         Log.d(TAG, "requestTvRecordingInfo");
1516                     }
1517                     if (mSessionCallback != null) {
1518                         mSessionCallback.onRequestTvRecordingInfo(recordingId);
1519                     }
1520                 } catch (RemoteException e) {
1521                     Log.w(TAG, "error in requestTvRecordingInfo", e);
1522                 }
1523             });
1524         }
1525 
1526         /**
1527          * Gets a list of {@link TvRecordingInfo} for the specified recording type.
1528          *
1529          * @param type The type of recording to retrieve.
1530          */
1531         @CallSuper
requestTvRecordingInfoList(@vRecordingInfo.TvRecordingListType int type)1532         public void requestTvRecordingInfoList(@TvRecordingInfo.TvRecordingListType int type) {
1533             executeOrPostRunnableOnMainThread(() -> {
1534                 try {
1535                     if (DEBUG) {
1536                         Log.d(TAG, "requestTvRecordingInfoList");
1537                     }
1538                     if (mSessionCallback != null) {
1539                         mSessionCallback.onRequestTvRecordingInfoList(type);
1540                     }
1541                 } catch (RemoteException e) {
1542                     Log.w(TAG, "error in requestTvRecordingInfoList", e);
1543                 }
1544             });
1545         }
1546 
1547         /**
1548          * Requests signing of the given data.
1549          *
1550          * <p>This is used when the corresponding server of the broadcast-independent interactive
1551          * app requires signing during handshaking, and the interactive app service doesn't have
1552          * the built-in private key. The private key is provided by the content providers and
1553          * pre-built in the related app, such as TV app.
1554          *
1555          * @param signingId the ID to identify the request. When a result is received, this ID can
1556          *                  be used to correlate the result with the request.
1557          * @param algorithm the standard name of the signature algorithm requested, such as
1558          *                  MD5withRSA, SHA256withDSA, etc. The name is from standards like
1559          *                  FIPS PUB 186-4 and PKCS #1.
1560          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
1561          * @param data the original bytes to be signed.
1562          *
1563          * @see #onSigningResult(String, byte[])
1564          * @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
1565          * @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
1566          */
1567         @CallSuper
requestSigning(@onNull String signingId, @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data)1568         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
1569                 @NonNull String alias, @NonNull byte[] data) {
1570             executeOrPostRunnableOnMainThread(new Runnable() {
1571                 @MainThread
1572                 @Override
1573                 public void run() {
1574                     try {
1575                         if (DEBUG) {
1576                             Log.d(TAG, "requestSigning");
1577                         }
1578                         if (mSessionCallback != null) {
1579                             mSessionCallback.onRequestSigning(signingId, algorithm, alias, data);
1580                         }
1581                     } catch (RemoteException e) {
1582                         Log.w(TAG, "error in requestSigning", e);
1583                     }
1584                 }
1585             });
1586         }
1587 
1588         /**
1589          * Sends an advertisement request to be processed by the related TV input.
1590          *
1591          * @param request The advertisement request
1592          */
1593         @CallSuper
requestAd(@onNull final AdRequest request)1594         public void requestAd(@NonNull final AdRequest request) {
1595             executeOrPostRunnableOnMainThread(new Runnable() {
1596                 @MainThread
1597                 @Override
1598                 public void run() {
1599                     try {
1600                         if (DEBUG) {
1601                             Log.d(TAG, "requestAd (id=" + request.getId() + ")");
1602                         }
1603                         if (mSessionCallback != null) {
1604                             mSessionCallback.onAdRequest(request);
1605                         }
1606                     } catch (RemoteException e) {
1607                         Log.w(TAG, "error in requestAd", e);
1608                     }
1609                 }
1610             });
1611         }
1612 
startInteractiveApp()1613         void startInteractiveApp() {
1614             onStartInteractiveApp();
1615         }
1616 
stopInteractiveApp()1617         void stopInteractiveApp() {
1618             onStopInteractiveApp();
1619         }
1620 
resetInteractiveApp()1621         void resetInteractiveApp() {
1622             onResetInteractiveApp();
1623         }
1624 
createBiInteractiveApp(@onNull Uri biIAppUri, @Nullable Bundle params)1625         void createBiInteractiveApp(@NonNull Uri biIAppUri, @Nullable Bundle params) {
1626             onCreateBiInteractiveAppRequest(biIAppUri, params);
1627         }
1628 
destroyBiInteractiveApp(@onNull String biIAppId)1629         void destroyBiInteractiveApp(@NonNull String biIAppId) {
1630             onDestroyBiInteractiveAppRequest(biIAppId);
1631         }
1632 
setTeletextAppEnabled(boolean enable)1633         void setTeletextAppEnabled(boolean enable) {
1634             onSetTeletextAppEnabled(enable);
1635         }
1636 
sendCurrentVideoBounds(@onNull Rect bounds)1637         void sendCurrentVideoBounds(@NonNull Rect bounds) {
1638             onCurrentVideoBounds(bounds);
1639         }
1640 
sendCurrentChannelUri(@ullable Uri channelUri)1641         void sendCurrentChannelUri(@Nullable Uri channelUri) {
1642             onCurrentChannelUri(channelUri);
1643         }
1644 
sendCurrentChannelLcn(int lcn)1645         void sendCurrentChannelLcn(int lcn) {
1646             onCurrentChannelLcn(lcn);
1647         }
1648 
sendStreamVolume(float volume)1649         void sendStreamVolume(float volume) {
1650             onStreamVolume(volume);
1651         }
1652 
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1653         void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
1654             onTrackInfoList(tracks);
1655         }
1656 
sendCurrentTvInputId(@ullable String inputId)1657         void sendCurrentTvInputId(@Nullable String inputId) {
1658             onCurrentTvInputId(inputId);
1659         }
1660 
sendTimeShiftMode(int mode)1661         void sendTimeShiftMode(int mode) {
1662             onTimeShiftMode(mode);
1663         }
1664 
sendAvailableSpeeds(@onNull float[] speeds)1665         void sendAvailableSpeeds(@NonNull float[] speeds) {
1666             onAvailableSpeeds(speeds);
1667         }
1668 
sendTvRecordingInfo(@ullable TvRecordingInfo recordingInfo)1669         void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
1670             onTvRecordingInfo(recordingInfo);
1671         }
1672 
sendTvRecordingInfoList(@ullable List<TvRecordingInfo> recordingInfoList)1673         void sendTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
1674             onTvRecordingInfoList(recordingInfoList);
1675         }
1676 
sendSigningResult(String signingId, byte[] result)1677         void sendSigningResult(String signingId, byte[] result) {
1678             onSigningResult(signingId, result);
1679         }
1680 
notifyError(String errMsg, Bundle params)1681         void notifyError(String errMsg, Bundle params) {
1682             onError(errMsg, params);
1683         }
1684 
release()1685         void release() {
1686             onRelease();
1687             if (mSurface != null) {
1688                 mSurface.release();
1689                 mSurface = null;
1690             }
1691             synchronized (mLock) {
1692                 mSessionCallback = null;
1693                 mPendingActions.clear();
1694             }
1695             // Removes the media view lastly so that any hanging on the main thread can be handled
1696             // in {@link #scheduleMediaViewCleanup}.
1697             removeMediaView(true);
1698         }
1699 
notifyTuned(Uri channelUri)1700         void notifyTuned(Uri channelUri) {
1701             if (DEBUG) {
1702                 Log.d(TAG, "notifyTuned (channelUri=" + channelUri + ")");
1703             }
1704             onTuned(channelUri);
1705         }
1706 
notifyTrackSelected(int type, String trackId)1707         void notifyTrackSelected(int type, String trackId) {
1708             if (DEBUG) {
1709                 Log.d(TAG, "notifyTrackSelected (type=" + type + "trackId=" + trackId + ")");
1710             }
1711             onTrackSelected(type, trackId);
1712         }
1713 
notifyTracksChanged(List<TvTrackInfo> tracks)1714         void notifyTracksChanged(List<TvTrackInfo> tracks) {
1715             if (DEBUG) {
1716                 Log.d(TAG, "notifyTracksChanged (tracks=" + tracks + ")");
1717             }
1718             onTracksChanged(tracks);
1719         }
1720 
notifyVideoAvailable()1721         void notifyVideoAvailable() {
1722             if (DEBUG) {
1723                 Log.d(TAG, "notifyVideoAvailable");
1724             }
1725             onVideoAvailable();
1726         }
1727 
notifyVideoUnavailable(int reason)1728         void notifyVideoUnavailable(int reason) {
1729             if (DEBUG) {
1730                 Log.d(TAG, "notifyVideoAvailable (reason=" + reason + ")");
1731             }
1732             onVideoUnavailable(reason);
1733         }
1734 
notifyContentAllowed()1735         void notifyContentAllowed() {
1736             if (DEBUG) {
1737                 Log.d(TAG, "notifyContentAllowed");
1738             }
1739             onContentAllowed();
1740         }
1741 
notifyContentBlocked(TvContentRating rating)1742         void notifyContentBlocked(TvContentRating rating) {
1743             if (DEBUG) {
1744                 Log.d(TAG, "notifyContentBlocked (rating=" + rating.flattenToString() + ")");
1745             }
1746             onContentBlocked(rating);
1747         }
1748 
notifySignalStrength(int strength)1749         void notifySignalStrength(int strength) {
1750             if (DEBUG) {
1751                 Log.d(TAG, "notifySignalStrength (strength=" + strength + ")");
1752             }
1753             onSignalStrength(strength);
1754         }
1755 
1756         /**
1757          * Calls {@link #onBroadcastInfoResponse}.
1758          */
notifyBroadcastInfoResponse(BroadcastInfoResponse response)1759         void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
1760             if (DEBUG) {
1761                 Log.d(TAG, "notifyBroadcastInfoResponse (requestId="
1762                         + response.getRequestId() + ")");
1763             }
1764             onBroadcastInfoResponse(response);
1765         }
1766 
1767         /**
1768          * Calls {@link #onAdResponse}.
1769          */
notifyAdResponse(AdResponse response)1770         void notifyAdResponse(AdResponse response) {
1771             if (DEBUG) {
1772                 Log.d(TAG, "notifyAdResponse (requestId=" + response.getId() + ")");
1773             }
1774             onAdResponse(response);
1775         }
1776 
notifyTvMessage(int type, Bundle data)1777         void notifyTvMessage(int type, Bundle data) {
1778             if (DEBUG) {
1779                 Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")");
1780             }
1781             onTvMessage(type, data);
1782         }
1783 
1784         /**
1785          * Calls {@link #onAdBufferConsumed}.
1786          */
notifyAdBufferConsumed(AdBuffer buffer)1787         void notifyAdBufferConsumed(AdBuffer buffer) {
1788             if (DEBUG) {
1789                 Log.d(TAG,
1790                         "notifyAdBufferConsumed (buffer=" + buffer + ")");
1791             }
1792             onAdBufferConsumed(buffer);
1793         }
1794 
1795         /**
1796          * Calls {@link #onRecordingStarted(String, String)}.
1797          */
notifyRecordingStarted(String recordingId, String requestId)1798         void notifyRecordingStarted(String recordingId, String requestId) {
1799             onRecordingStarted(recordingId, requestId);
1800         }
1801 
1802         /**
1803          * Calls {@link #onRecordingStopped(String)}.
1804          */
notifyRecordingStopped(String recordingId)1805         void notifyRecordingStopped(String recordingId) {
1806             onRecordingStopped(recordingId);
1807         }
1808 
1809         /**
1810          * Calls {@link #onRecordingConnectionFailed(String, String)}.
1811          */
notifyRecordingConnectionFailed(String recordingId, String inputId)1812         void notifyRecordingConnectionFailed(String recordingId, String inputId) {
1813             onRecordingConnectionFailed(recordingId, inputId);
1814         }
1815 
1816         /**
1817          * Calls {@link #onRecordingDisconnected(String, String)}.
1818          */
notifyRecordingDisconnected(String recordingId, String inputId)1819         void notifyRecordingDisconnected(String recordingId, String inputId) {
1820             onRecordingDisconnected(recordingId, inputId);
1821         }
1822 
1823         /**
1824          * Calls {@link #onRecordingTuned(String, Uri)}.
1825          */
notifyRecordingTuned(String recordingId, Uri channelUri)1826         void notifyRecordingTuned(String recordingId, Uri channelUri) {
1827             onRecordingTuned(recordingId, channelUri);
1828         }
1829 
1830         /**
1831          * Calls {@link #onRecordingError(String, int)}.
1832          */
notifyRecordingError(String recordingId, int err)1833         void notifyRecordingError(String recordingId, int err) {
1834             onRecordingError(recordingId, err);
1835         }
1836 
1837         /**
1838          * Calls {@link #onRecordingScheduled(String, String)}.
1839          */
notifyRecordingScheduled(String recordingId, String requestId)1840         void notifyRecordingScheduled(String recordingId, String requestId) {
1841             onRecordingScheduled(recordingId, requestId);
1842         }
1843 
1844         /**
1845          * Calls {@link #onTimeShiftPlaybackParams(PlaybackParams)}.
1846          */
notifyTimeShiftPlaybackParams(PlaybackParams params)1847         void notifyTimeShiftPlaybackParams(PlaybackParams params) {
1848             onTimeShiftPlaybackParams(params);
1849         }
1850 
1851         /**
1852          * Calls {@link #onTimeShiftStatusChanged(String, int)}.
1853          */
notifyTimeShiftStatusChanged(String inputId, int status)1854         void notifyTimeShiftStatusChanged(String inputId, int status) {
1855             onTimeShiftStatusChanged(inputId, status);
1856         }
1857 
1858         /**
1859          * Calls {@link #onTimeShiftStartPositionChanged(String, long)}.
1860          */
notifyTimeShiftStartPositionChanged(String inputId, long timeMs)1861         void notifyTimeShiftStartPositionChanged(String inputId, long timeMs) {
1862             onTimeShiftStartPositionChanged(inputId, timeMs);
1863         }
1864 
1865         /**
1866          * Calls {@link #onTimeShiftCurrentPositionChanged(String, long)}.
1867          */
notifyTimeShiftCurrentPositionChanged(String inputId, long timeMs)1868         void notifyTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
1869             onTimeShiftCurrentPositionChanged(inputId, timeMs);
1870         }
1871 
1872         /**
1873          * Notifies when the session state is changed.
1874          *
1875          * @param state the current session state.
1876          * @param err the error code for error state. {@link TvInteractiveAppManager#ERROR_NONE} is
1877          *            used when the state is not
1878          *            {@link TvInteractiveAppManager#INTERACTIVE_APP_STATE_ERROR}.
1879          */
1880         @CallSuper
notifySessionStateChanged( @vInteractiveAppManager.InteractiveAppState int state, @TvInteractiveAppManager.ErrorCode int err)1881         public void notifySessionStateChanged(
1882                 @TvInteractiveAppManager.InteractiveAppState int state,
1883                 @TvInteractiveAppManager.ErrorCode int err) {
1884             executeOrPostRunnableOnMainThread(new Runnable() {
1885                 @MainThread
1886                 @Override
1887                 public void run() {
1888                     try {
1889                         if (DEBUG) {
1890                             Log.d(TAG, "notifySessionStateChanged (state="
1891                                     + state + "; err=" + err + ")");
1892                         }
1893                         if (mSessionCallback != null) {
1894                             mSessionCallback.onSessionStateChanged(state, err);
1895                         }
1896                     } catch (RemoteException e) {
1897                         Log.w(TAG, "error in notifySessionStateChanged", e);
1898                     }
1899                 }
1900             });
1901         }
1902 
1903         /**
1904          * Notifies the broadcast-independent(BI) interactive application has been created.
1905          *
1906          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
1907          *                 app. {@code null} if it's not created successfully.
1908          *
1909          * @see #onCreateBiInteractiveAppRequest(Uri, Bundle)
1910          */
1911         @CallSuper
notifyBiInteractiveAppCreated( @onNull Uri biIAppUri, @Nullable String biIAppId)1912         public final void notifyBiInteractiveAppCreated(
1913                 @NonNull Uri biIAppUri, @Nullable String biIAppId) {
1914             executeOrPostRunnableOnMainThread(new Runnable() {
1915                 @MainThread
1916                 @Override
1917                 public void run() {
1918                     try {
1919                         if (DEBUG) {
1920                             Log.d(TAG, "notifyBiInteractiveAppCreated (biIAppId="
1921                                     + biIAppId + ")");
1922                         }
1923                         if (mSessionCallback != null) {
1924                             mSessionCallback.onBiInteractiveAppCreated(biIAppUri, biIAppId);
1925                         }
1926                     } catch (RemoteException e) {
1927                         Log.w(TAG, "error in notifyBiInteractiveAppCreated", e);
1928                     }
1929                 }
1930             });
1931         }
1932 
1933         /**
1934          * Notifies when the digital teletext app state is changed.
1935          * @param state the current state.
1936          */
1937         @CallSuper
notifyTeletextAppStateChanged( @vInteractiveAppManager.TeletextAppState int state)1938         public final void notifyTeletextAppStateChanged(
1939                 @TvInteractiveAppManager.TeletextAppState int state) {
1940             executeOrPostRunnableOnMainThread(new Runnable() {
1941                 @MainThread
1942                 @Override
1943                 public void run() {
1944                     try {
1945                         if (DEBUG) {
1946                             Log.d(TAG, "notifyTeletextAppState (state="
1947                                     + state + ")");
1948                         }
1949                         if (mSessionCallback != null) {
1950                             mSessionCallback.onTeletextAppStateChanged(state);
1951                         }
1952                     } catch (RemoteException e) {
1953                         Log.w(TAG, "error in notifyTeletextAppState", e);
1954                     }
1955                 }
1956             });
1957         }
1958 
1959 
1960         /**
1961          * Notifies when the advertisement buffer is filled and ready to be read.
1962          *
1963          * @param buffer The {@link AdBuffer} to be received
1964          */
1965         @CallSuper
notifyAdBufferReady(@onNull AdBuffer buffer)1966         public void notifyAdBufferReady(@NonNull AdBuffer buffer) {
1967             AdBuffer dupBuffer;
1968             try {
1969                 dupBuffer = AdBuffer.dupAdBuffer(buffer);
1970             } catch (IOException e) {
1971                 Log.w(TAG, "dup AdBuffer error in notifyAdBufferReady:", e);
1972                 return;
1973             }
1974             executeOrPostRunnableOnMainThread(new Runnable() {
1975                 @MainThread
1976                 @Override
1977                 public void run() {
1978                     try {
1979                         if (DEBUG) {
1980                             Log.d(TAG,
1981                                     "notifyAdBufferReady(buffer=" + buffer + ")");
1982                         }
1983                         if (mSessionCallback != null) {
1984                             mSessionCallback.onAdBufferReady(dupBuffer);
1985                         }
1986                     } catch (RemoteException e) {
1987                         Log.w(TAG, "error in notifyAdBuffer", e);
1988                     } finally {
1989                         if (dupBuffer != null) {
1990                             dupBuffer.getSharedMemory().close();
1991                         }
1992                     }
1993                 }
1994             });
1995         }
1996 
1997 
1998         /**
1999          * Takes care of dispatching incoming input events and tells whether the event was handled.
2000          */
dispatchInputEvent(InputEvent event, InputEventReceiver receiver)2001         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
2002             if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
2003             if (event instanceof KeyEvent) {
2004                 KeyEvent keyEvent = (KeyEvent) event;
2005                 if (keyEvent.dispatch(this, mDispatcherState, this)) {
2006                     return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
2007                 }
2008 
2009                 // TODO: special handlings of navigation keys and media keys
2010             } else if (event instanceof MotionEvent) {
2011                 MotionEvent motionEvent = (MotionEvent) event;
2012                 final int source = motionEvent.getSource();
2013                 if (motionEvent.isTouchEvent()) {
2014                     if (onTouchEvent(motionEvent)) {
2015                         return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
2016                     }
2017                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
2018                     if (onTrackballEvent(motionEvent)) {
2019                         return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
2020                     }
2021                 } else {
2022                     if (onGenericMotionEvent(motionEvent)) {
2023                         return TvInteractiveAppManager.Session.DISPATCH_HANDLED;
2024                     }
2025                 }
2026             }
2027             // TODO: handle overlay view
2028             return TvInteractiveAppManager.Session.DISPATCH_NOT_HANDLED;
2029         }
2030 
initialize(ITvInteractiveAppSessionCallback callback)2031         private void initialize(ITvInteractiveAppSessionCallback callback) {
2032             synchronized (mLock) {
2033                 mSessionCallback = callback;
2034                 for (Runnable runnable : mPendingActions) {
2035                     runnable.run();
2036                 }
2037                 mPendingActions.clear();
2038             }
2039         }
2040 
2041         /**
2042          * Calls {@link #onSetSurface}.
2043          */
setSurface(Surface surface)2044         void setSurface(Surface surface) {
2045             onSetSurface(surface);
2046             if (mSurface != null) {
2047                 mSurface.release();
2048             }
2049             mSurface = surface;
2050             // TODO: Handle failure.
2051         }
2052 
2053         /**
2054          * Calls {@link #onSurfaceChanged}.
2055          */
dispatchSurfaceChanged(int format, int width, int height)2056         void dispatchSurfaceChanged(int format, int width, int height) {
2057             if (DEBUG) {
2058                 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
2059                         + ", height=" + height + ")");
2060             }
2061             onSurfaceChanged(format, width, height);
2062         }
2063 
executeOrPostRunnableOnMainThread(Runnable action)2064         private void executeOrPostRunnableOnMainThread(Runnable action) {
2065             synchronized (mLock) {
2066                 if (mSessionCallback == null) {
2067                     // The session is not initialized yet.
2068                     mPendingActions.add(action);
2069                 } else {
2070                     if (mHandler.getLooper().isCurrentThread()) {
2071                         action.run();
2072                     } else {
2073                         // Posts the runnable if this is not called from the main thread
2074                         mHandler.post(action);
2075                     }
2076                 }
2077             }
2078         }
2079 
2080         /**
2081          * Creates an media view. This calls {@link #onCreateMediaView} to get a view to attach
2082          * to the media window.
2083          *
2084          * @param windowToken A window token of the application.
2085          * @param frame A position of the media view.
2086          */
createMediaView(IBinder windowToken, Rect frame)2087         void createMediaView(IBinder windowToken, Rect frame) {
2088             if (mMediaViewContainer != null) {
2089                 removeMediaView(false);
2090             }
2091             if (DEBUG) Log.d(TAG, "create media view(" + frame + ")");
2092             mWindowToken = windowToken;
2093             mMediaFrame = frame;
2094             onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
2095             if (!mMediaViewEnabled) {
2096                 return;
2097             }
2098             mMediaView = onCreateMediaView();
2099             if (mMediaView == null) {
2100                 return;
2101             }
2102             if (mMediaViewCleanUpTask != null) {
2103                 mMediaViewCleanUpTask.cancel(true);
2104                 mMediaViewCleanUpTask = null;
2105             }
2106             // Creates a container view to check hanging on the media view detaching.
2107             // Adding/removing the media view to/from the container make the view attach/detach
2108             // logic run on the main thread.
2109             mMediaViewContainer = new FrameLayout(mContext.getApplicationContext());
2110             mMediaViewContainer.addView(mMediaView);
2111 
2112             int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
2113             // We make the overlay view non-focusable and non-touchable so that
2114             // the application that owns the window token can decide whether to consume or
2115             // dispatch the input events.
2116             int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
2117                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
2118                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
2119             if (ActivityManager.isHighEndGfx()) {
2120                 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
2121             }
2122             mWindowParams = new WindowManager.LayoutParams(
2123                     frame.right - frame.left, frame.bottom - frame.top,
2124                     frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
2125             mWindowParams.privateFlags |=
2126                     WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
2127             mWindowParams.gravity = Gravity.START | Gravity.TOP;
2128             mWindowParams.token = windowToken;
2129             mWindowManager.addView(mMediaViewContainer, mWindowParams);
2130         }
2131 
2132         /**
2133          * Relayouts the current media view.
2134          *
2135          * @param frame A new position of the media view.
2136          */
relayoutMediaView(Rect frame)2137         void relayoutMediaView(Rect frame) {
2138             if (DEBUG) Log.d(TAG, "relayoutMediaView(" + frame + ")");
2139             if (mMediaFrame == null || mMediaFrame.width() != frame.width()
2140                     || mMediaFrame.height() != frame.height()) {
2141                 // Note: relayoutMediaView is called whenever TvInteractiveAppView's layout is
2142                 // changed regardless of setMediaViewEnabled.
2143                 onMediaViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
2144             }
2145             mMediaFrame = frame;
2146             if (!mMediaViewEnabled || mMediaViewContainer == null) {
2147                 return;
2148             }
2149             mWindowParams.x = frame.left;
2150             mWindowParams.y = frame.top;
2151             mWindowParams.width = frame.right - frame.left;
2152             mWindowParams.height = frame.bottom - frame.top;
2153             mWindowManager.updateViewLayout(mMediaViewContainer, mWindowParams);
2154         }
2155 
2156         /**
2157          * Removes the current media view.
2158          */
removeMediaView(boolean clearWindowToken)2159         void removeMediaView(boolean clearWindowToken) {
2160             if (DEBUG) Log.d(TAG, "removeMediaView(" + mMediaViewContainer + ")");
2161             if (clearWindowToken) {
2162                 mWindowToken = null;
2163                 mMediaFrame = null;
2164             }
2165             if (mMediaViewContainer != null) {
2166                 // Removes the media view from the view hierarchy in advance so that it can be
2167                 // cleaned up in the {@link MediaViewCleanUpTask} if the remove process is
2168                 // hanging.
2169                 mMediaViewContainer.removeView(mMediaView);
2170                 mMediaView = null;
2171                 mWindowManager.removeView(mMediaViewContainer);
2172                 mMediaViewContainer = null;
2173                 mWindowParams = null;
2174             }
2175         }
2176 
2177         /**
2178          * Schedules a task which checks whether the media view is detached and kills the process
2179          * if it is not. Note that this method is expected to be called in a non-main thread.
2180          */
scheduleMediaViewCleanup()2181         void scheduleMediaViewCleanup() {
2182             View mediaViewParent = mMediaViewContainer;
2183             if (mediaViewParent != null) {
2184                 mMediaViewCleanUpTask = new MediaViewCleanUpTask();
2185                 mMediaViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
2186                         mediaViewParent);
2187             }
2188         }
2189     }
2190 
2191     private static final class MediaViewCleanUpTask extends AsyncTask<View, Void, Void> {
2192         @Override
doInBackground(View... views)2193         protected Void doInBackground(View... views) {
2194             View mediaViewParent = views[0];
2195             try {
2196                 Thread.sleep(DETACH_MEDIA_VIEW_TIMEOUT_MS);
2197             } catch (InterruptedException e) {
2198                 return null;
2199             }
2200             if (isCancelled()) {
2201                 return null;
2202             }
2203             if (mediaViewParent.isAttachedToWindow()) {
2204                 Log.e(TAG, "Time out on releasing media view. Killing "
2205                         + mediaViewParent.getContext().getPackageName());
2206                 android.os.Process.killProcess(Process.myPid());
2207             }
2208             return null;
2209         }
2210     }
2211 
2212     @SuppressLint("HandlerLeak")
2213     private final class ServiceHandler extends Handler {
2214         private static final int DO_CREATE_SESSION = 1;
2215         private static final int DO_NOTIFY_SESSION_CREATED = 2;
2216         private static final int DO_NOTIFY_RTE_STATE_CHANGED = 3;
2217 
broadcastRteStateChanged(int type, int state, int error)2218         private void broadcastRteStateChanged(int type, int state, int error) {
2219             int n = mCallbacks.beginBroadcast();
2220             for (int i = 0; i < n; ++i) {
2221                 try {
2222                     mCallbacks.getBroadcastItem(i).onStateChanged(type, state, error);
2223                 } catch (RemoteException e) {
2224                     Log.e(TAG, "error in broadcastRteStateChanged", e);
2225                 }
2226             }
2227             mCallbacks.finishBroadcast();
2228         }
2229 
2230         @Override
handleMessage(Message msg)2231         public void handleMessage(Message msg) {
2232             switch (msg.what) {
2233                 case DO_CREATE_SESSION: {
2234                     SomeArgs args = (SomeArgs) msg.obj;
2235                     InputChannel channel = (InputChannel) args.arg1;
2236                     ITvInteractiveAppSessionCallback cb =
2237                             (ITvInteractiveAppSessionCallback) args.arg2;
2238                     String iAppServiceId = (String) args.arg3;
2239                     int type = (int) args.arg4;
2240                     args.recycle();
2241                     Session sessionImpl = onCreateSession(iAppServiceId, type);
2242                     if (sessionImpl == null) {
2243                         try {
2244                             // Failed to create a session.
2245                             cb.onSessionCreated(null);
2246                         } catch (RemoteException e) {
2247                             Log.e(TAG, "error in onSessionCreated", e);
2248                         }
2249                         return;
2250                     }
2251                     ITvInteractiveAppSession stub = new ITvInteractiveAppSessionWrapper(
2252                             TvInteractiveAppService.this, sessionImpl, channel);
2253 
2254                     SomeArgs someArgs = SomeArgs.obtain();
2255                     someArgs.arg1 = sessionImpl;
2256                     someArgs.arg2 = stub;
2257                     someArgs.arg3 = cb;
2258                     mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
2259                             someArgs).sendToTarget();
2260                     return;
2261                 }
2262                 case DO_NOTIFY_SESSION_CREATED: {
2263                     SomeArgs args = (SomeArgs) msg.obj;
2264                     Session sessionImpl = (Session) args.arg1;
2265                     ITvInteractiveAppSession stub = (ITvInteractiveAppSession) args.arg2;
2266                     ITvInteractiveAppSessionCallback cb =
2267                             (ITvInteractiveAppSessionCallback) args.arg3;
2268                     try {
2269                         cb.onSessionCreated(stub);
2270                     } catch (RemoteException e) {
2271                         Log.e(TAG, "error in onSessionCreated", e);
2272                     }
2273                     if (sessionImpl != null) {
2274                         sessionImpl.initialize(cb);
2275                     }
2276                     args.recycle();
2277                     return;
2278                 }
2279                 case DO_NOTIFY_RTE_STATE_CHANGED: {
2280                     SomeArgs args = (SomeArgs) msg.obj;
2281                     int type = (int) args.arg1;
2282                     int state = (int) args.arg2;
2283                     int error = (int) args.arg3;
2284                     broadcastRteStateChanged(type, state, error);
2285                     return;
2286                 }
2287                 default: {
2288                     Log.w(TAG, "Unhandled message code: " + msg.what);
2289                     return;
2290                 }
2291             }
2292         }
2293 
2294     }
2295 }
2296