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.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemService;
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.media.PlaybackParams;
27 import android.media.tv.AdBuffer;
28 import android.media.tv.AdRequest;
29 import android.media.tv.AdResponse;
30 import android.media.tv.BroadcastInfoRequest;
31 import android.media.tv.BroadcastInfoResponse;
32 import android.media.tv.TvContentRating;
33 import android.media.tv.TvInputManager;
34 import android.media.tv.TvRecordingInfo;
35 import android.media.tv.TvTrackInfo;
36 import android.media.tv.interactive.TvInteractiveAppService.Session;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Looper;
42 import android.os.Message;
43 import android.os.RemoteException;
44 import android.util.Log;
45 import android.util.Pools;
46 import android.util.SparseArray;
47 import android.view.InputChannel;
48 import android.view.InputEvent;
49 import android.view.InputEventSender;
50 import android.view.Surface;
51 import android.view.View;
52 
53 import com.android.internal.util.Preconditions;
54 
55 import java.lang.annotation.Retention;
56 import java.lang.annotation.RetentionPolicy;
57 import java.util.ArrayList;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.concurrent.Executor;
61 
62 /**
63  * Central system API to the overall TV interactive application framework (TIAF) architecture, which
64  * arbitrates interaction between Android applications and TV interactive apps.
65  */
66 @SystemService(Context.TV_INTERACTIVE_APP_SERVICE)
67 public final class TvInteractiveAppManager {
68     // TODO: cleanup and unhide public APIs
69     private static final String TAG = "TvInteractiveAppManager";
70 
71     /** @hide */
72     @Retention(RetentionPolicy.SOURCE)
73     @IntDef(flag = false, prefix = "SERVICE_STATE_", value = {
74             SERVICE_STATE_UNREALIZED,
75             SERVICE_STATE_PREPARING,
76             SERVICE_STATE_READY,
77             SERVICE_STATE_ERROR})
78     public @interface ServiceState {}
79 
80     /**
81      * Unrealized state of interactive app service.
82      */
83     public static final int SERVICE_STATE_UNREALIZED = 1;
84     /**
85      * Preparing state of interactive app service.
86      */
87     public static final int SERVICE_STATE_PREPARING = 2;
88     /**
89      * Ready state of interactive app service.
90      *
91      * <p>In this state, the interactive app service is ready, and interactive apps can be started.
92      *
93      * @see TvInteractiveAppView#startInteractiveApp()
94      */
95     public static final int SERVICE_STATE_READY = 3;
96     /**
97      * Error state of interactive app service.
98      */
99     public static final int SERVICE_STATE_ERROR = 4;
100 
101 
102     /** @hide */
103     @Retention(RetentionPolicy.SOURCE)
104     @IntDef(flag = false, prefix = "INTERACTIVE_APP_STATE_", value = {
105             INTERACTIVE_APP_STATE_STOPPED,
106             INTERACTIVE_APP_STATE_RUNNING,
107             INTERACTIVE_APP_STATE_ERROR})
108     public @interface InteractiveAppState {}
109 
110     /**
111      * Stopped (or not started) state of interactive application.
112      */
113     public static final int INTERACTIVE_APP_STATE_STOPPED = 1;
114     /**
115      * Running state of interactive application.
116      */
117     public static final int INTERACTIVE_APP_STATE_RUNNING = 2;
118     /**
119      * Error state of interactive application.
120      */
121     public static final int INTERACTIVE_APP_STATE_ERROR = 3;
122 
123 
124     /** @hide */
125     @Retention(RetentionPolicy.SOURCE)
126     @IntDef(flag = false, prefix = "ERROR_", value = {
127             ERROR_NONE,
128             ERROR_UNKNOWN,
129             ERROR_NOT_SUPPORTED,
130             ERROR_WEAK_SIGNAL,
131             ERROR_RESOURCE_UNAVAILABLE,
132             ERROR_BLOCKED,
133             ERROR_ENCRYPTED,
134             ERROR_UNKNOWN_CHANNEL,
135     })
136     public @interface ErrorCode {}
137 
138     /**
139      * No error.
140      */
141     public static final int ERROR_NONE = 0;
142     /**
143      * Unknown error code.
144      */
145     public static final int ERROR_UNKNOWN = 1;
146     /**
147      * Error code for an unsupported channel.
148      */
149     public static final int ERROR_NOT_SUPPORTED = 2;
150     /**
151      * Error code for weak signal.
152      */
153     public static final int ERROR_WEAK_SIGNAL = 3;
154     /**
155      * Error code when resource (e.g. tuner) is unavailable.
156      */
157     public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
158     /**
159      * Error code for blocked contents.
160      */
161     public static final int ERROR_BLOCKED = 5;
162     /**
163      * Error code when the key or module is missing for the encrypted channel.
164      */
165     public static final int ERROR_ENCRYPTED = 6;
166     /**
167      * Error code when the current channel is an unknown channel.
168      */
169     public static final int ERROR_UNKNOWN_CHANNEL = 7;
170 
171     /** @hide */
172     @Retention(RetentionPolicy.SOURCE)
173     @IntDef(flag = false, prefix = "TELETEXT_APP_STATE_", value = {
174             TELETEXT_APP_STATE_SHOW,
175             TELETEXT_APP_STATE_HIDE,
176             TELETEXT_APP_STATE_ERROR})
177     public @interface TeletextAppState {}
178 
179     /**
180      * State of Teletext app: show
181      */
182     public static final int TELETEXT_APP_STATE_SHOW = 1;
183     /**
184      * State of Teletext app: hide
185      */
186     public static final int TELETEXT_APP_STATE_HIDE = 2;
187     /**
188      * State of Teletext app: error
189      */
190     public static final int TELETEXT_APP_STATE_ERROR = 3;
191 
192     /**
193      * Key for package name in app link.
194      * <p>Type: String
195      *
196      * @see #sendAppLinkCommand(String, Bundle)
197      */
198     public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
199 
200     /**
201      * Key for class name in app link.
202      * <p>Type: String
203      *
204      * @see #sendAppLinkCommand(String, Bundle)
205      */
206     public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
207 
208     /**
209      * Key for command type in app link command.
210      * <p>Type: String
211      *
212      * @see #sendAppLinkCommand(String, Bundle)
213      */
214     public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
215 
216     /**
217      * Key for service ID in app link command.
218      * <p>Type: String
219      *
220      * @see #sendAppLinkCommand(String, Bundle)
221      */
222     public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
223 
224     /**
225      * Key for back URI in app link command.
226      * <p>Type: String
227      *
228      * @see #sendAppLinkCommand(String, Bundle)
229      */
230     public static final String APP_LINK_KEY_BACK_URI = "back_uri";
231 
232     /**
233      * Broadcast intent action to send app command to TV app.
234      *
235      * @see #sendAppLinkCommand(String, Bundle)
236      */
237     public static final String ACTION_APP_LINK_COMMAND =
238             "android.media.tv.interactive.action.APP_LINK_COMMAND";
239 
240     /**
241      * Intent key for TV input ID. It's used to send app command to TV app.
242      * <p>Type: String
243      *
244      * @see #sendAppLinkCommand(String, Bundle)
245      * @see #ACTION_APP_LINK_COMMAND
246      */
247     public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
248 
249     /**
250      * Intent key for TV interactive app ID. It's used to send app command to TV app.
251      * <p>Type: String
252      *
253      * @see #sendAppLinkCommand(String, Bundle)
254      * @see #ACTION_APP_LINK_COMMAND
255      * @see TvInteractiveAppServiceInfo#getId()
256      */
257     public static final String INTENT_KEY_INTERACTIVE_APP_SERVICE_ID = "interactive_app_id";
258 
259     /**
260      * Intent key for TV channel URI. It's used to send app command to TV app.
261      * <p>Type: android.net.Uri
262      *
263      * @see #sendAppLinkCommand(String, Bundle)
264      * @see #ACTION_APP_LINK_COMMAND
265      */
266     public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
267 
268     /**
269      * Intent key for broadcast-independent(BI) interactive app type. It's used to send app command
270      * to TV app.
271      * <p>Type: int
272      *
273      * @see #sendAppLinkCommand(String, Bundle)
274      * @see #ACTION_APP_LINK_COMMAND
275      * @see android.media.tv.interactive.TvInteractiveAppServiceInfo#getSupportedTypes()
276      * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
277      */
278     public static final String INTENT_KEY_BI_INTERACTIVE_APP_TYPE = "bi_interactive_app_type";
279 
280     /**
281      * Intent key for broadcast-independent(BI) interactive app URI. It's used to send app command
282      * to TV app.
283      * <p>Type: android.net.Uri
284      *
285      * @see #sendAppLinkCommand(String, Bundle)
286      * @see #ACTION_APP_LINK_COMMAND
287      * @see android.media.tv.interactive.TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
288      */
289     public static final String INTENT_KEY_BI_INTERACTIVE_APP_URI = "bi_interactive_app_uri";
290 
291     /**
292      * Intent key for command type. It's used to send app command to TV app. The value of this key
293      * could vary according to TV apps.
294      * <p>Type: String
295      *
296      * @see #sendAppLinkCommand(String, Bundle)
297      * @see #ACTION_APP_LINK_COMMAND
298      */
299     public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
300 
301     private final ITvInteractiveAppManager mService;
302     private final int mUserId;
303 
304     // A mapping from the sequence number of a session to its SessionCallbackRecord.
305     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
306             new SparseArray<>();
307 
308     // @GuardedBy("mLock")
309     private final List<TvInteractiveAppCallbackRecord> mCallbackRecords = new ArrayList<>();
310 
311     // A sequence number for the next session to be created. Should be protected by a lock
312     // {@code mSessionCallbackRecordMap}.
313     private int mNextSeq;
314 
315     private final Object mLock = new Object();
316 
317     private final ITvInteractiveAppClient mClient;
318 
319     /** @hide */
TvInteractiveAppManager(ITvInteractiveAppManager service, int userId)320     public TvInteractiveAppManager(ITvInteractiveAppManager service, int userId) {
321         mService = service;
322         mUserId = userId;
323         mClient = new ITvInteractiveAppClient.Stub() {
324             @Override
325             public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel,
326                     int seq) {
327                 synchronized (mSessionCallbackRecordMap) {
328                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
329                     if (record == null) {
330                         Log.e(TAG, "Callback not found for " + token);
331                         return;
332                     }
333                     Session session = null;
334                     if (token != null) {
335                         session = new Session(token, channel, mService, mUserId, seq,
336                                 mSessionCallbackRecordMap);
337                     } else {
338                         mSessionCallbackRecordMap.delete(seq);
339                     }
340                     record.postSessionCreated(session);
341                 }
342             }
343 
344             @Override
345             public void onSessionReleased(int seq) {
346                 synchronized (mSessionCallbackRecordMap) {
347                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
348                     mSessionCallbackRecordMap.delete(seq);
349                     if (record == null) {
350                         Log.e(TAG, "Callback not found for seq:" + seq);
351                         return;
352                     }
353                     record.mSession.releaseInternal();
354                     record.postSessionReleased();
355                 }
356             }
357 
358             @Override
359             public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
360                 synchronized (mSessionCallbackRecordMap) {
361                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
362                     if (record == null) {
363                         Log.e(TAG, "Callback not found for seq " + seq);
364                         return;
365                     }
366                     record.postLayoutSurface(left, top, right, bottom);
367                 }
368             }
369 
370             @Override
371             public void onBroadcastInfoRequest(BroadcastInfoRequest request, int seq) {
372                 synchronized (mSessionCallbackRecordMap) {
373                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
374                     if (record == null) {
375                         Log.e(TAG, "Callback not found for seq " + seq);
376                         return;
377                     }
378                     record.postBroadcastInfoRequest(request);
379                 }
380             }
381 
382             @Override
383             public void onRemoveBroadcastInfo(int requestId, int seq) {
384                 synchronized (mSessionCallbackRecordMap) {
385                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
386                     if (record == null) {
387                         Log.e(TAG, "Callback not found for seq " + seq);
388                         return;
389                     }
390                     record.postRemoveBroadcastInfo(requestId);
391                 }
392             }
393 
394             @Override
395             public void onCommandRequest(
396                     @TvInteractiveAppService.PlaybackCommandType String cmdType,
397                     Bundle parameters,
398                     int seq) {
399                 synchronized (mSessionCallbackRecordMap) {
400                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
401                     if (record == null) {
402                         Log.e(TAG, "Callback not found for seq " + seq);
403                         return;
404                     }
405                     record.postCommandRequest(cmdType, parameters);
406                 }
407             }
408 
409             @Override
410             public void onTimeShiftCommandRequest(
411                     @TvInteractiveAppService.TimeShiftCommandType String cmdType,
412                     Bundle parameters,
413                     int seq) {
414                 synchronized (mSessionCallbackRecordMap) {
415                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
416                     if (record == null) {
417                         Log.e(TAG, "Callback not found for seq " + seq);
418                         return;
419                     }
420                     record.postTimeShiftCommandRequest(cmdType, parameters);
421                 }
422             }
423 
424             @Override
425             public void onSetVideoBounds(Rect rect, int seq) {
426                 synchronized (mSessionCallbackRecordMap) {
427                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
428                     if (record == null) {
429                         Log.e(TAG, "Callback not found for seq " + seq);
430                         return;
431                     }
432                     record.postSetVideoBounds(rect);
433                 }
434             }
435 
436             @Override
437             public void onAdRequest(AdRequest request, int seq) {
438                 synchronized (mSessionCallbackRecordMap) {
439                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
440                     if (record == null) {
441                         Log.e(TAG, "Callback not found for seq " + seq);
442                         return;
443                     }
444                     record.postAdRequest(request);
445                 }
446             }
447 
448             @Override
449             public void onRequestCurrentVideoBounds(int seq) {
450                 synchronized (mSessionCallbackRecordMap) {
451                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
452                     if (record == null) {
453                         Log.e(TAG, "Callback not found for seq " + seq);
454                         return;
455                     }
456                     record.postRequestCurrentVideoBounds();
457                 }
458             }
459 
460             @Override
461             public void onRequestCurrentChannelUri(int seq) {
462                 synchronized (mSessionCallbackRecordMap) {
463                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
464                     if (record == null) {
465                         Log.e(TAG, "Callback not found for seq " + seq);
466                         return;
467                     }
468                     record.postRequestCurrentChannelUri();
469                 }
470             }
471 
472             @Override
473             public void onRequestCurrentChannelLcn(int seq) {
474                 synchronized (mSessionCallbackRecordMap) {
475                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
476                     if (record == null) {
477                         Log.e(TAG, "Callback not found for seq " + seq);
478                         return;
479                     }
480                     record.postRequestCurrentChannelLcn();
481                 }
482             }
483 
484             @Override
485             public void onRequestStreamVolume(int seq) {
486                 synchronized (mSessionCallbackRecordMap) {
487                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
488                     if (record == null) {
489                         Log.e(TAG, "Callback not found for seq " + seq);
490                         return;
491                     }
492                     record.postRequestStreamVolume();
493                 }
494             }
495 
496             @Override
497             public void onRequestTrackInfoList(int seq) {
498                 synchronized (mSessionCallbackRecordMap) {
499                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
500                     if (record == null) {
501                         Log.e(TAG, "Callback not found for seq " + seq);
502                         return;
503                     }
504                     record.postRequestTrackInfoList();
505                 }
506             }
507 
508             @Override
509             public void onRequestCurrentTvInputId(int seq) {
510                 synchronized (mSessionCallbackRecordMap) {
511                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
512                     if (record == null) {
513                         Log.e(TAG, "Callback not found for seq " + seq);
514                         return;
515                     }
516                     record.postRequestCurrentTvInputId();
517                 }
518             }
519 
520             @Override
521             public void onRequestTimeShiftMode(int seq) {
522                 synchronized (mSessionCallbackRecordMap) {
523                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
524                     if (record == null) {
525                         Log.e(TAG, "Callback not found for seq " + seq);
526                         return;
527                     }
528                     record.postRequestTimeShiftMode();
529                 }
530             }
531 
532             @Override
533             public void onRequestAvailableSpeeds(int seq) {
534                 synchronized (mSessionCallbackRecordMap) {
535                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
536                     if (record == null) {
537                         Log.e(TAG, "Callback not found for seq " + seq);
538                         return;
539                     }
540                     record.postRequestAvailableSpeeds();
541                 }
542             }
543 
544             @Override
545             public void onRequestStartRecording(String requestId, Uri programUri, int seq) {
546                 synchronized (mSessionCallbackRecordMap) {
547                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
548                     if (record == null) {
549                         Log.e(TAG, "Callback not found for seq " + seq);
550                         return;
551                     }
552                     record.postRequestStartRecording(requestId, programUri);
553                 }
554             }
555 
556             @Override
557             public void onRequestStopRecording(String recordingId, int seq) {
558                 synchronized (mSessionCallbackRecordMap) {
559                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
560                     if (record == null) {
561                         Log.e(TAG, "Callback not found for seq " + seq);
562                         return;
563                     }
564                     record.postRequestStopRecording(recordingId);
565                 }
566             }
567 
568             @Override
569             public void onRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
570                     Uri programUri, Bundle params, int seq) {
571                 synchronized (mSessionCallbackRecordMap) {
572                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
573                     if (record == null) {
574                         Log.e(TAG, "Callback not found for seq " + seq);
575                         return;
576                     }
577                     record.postRequestScheduleRecording(
578                             requestId, inputId, channelUri, programUri, params);
579                 }
580             }
581 
582             @Override
583             public void onRequestScheduleRecording2(String requestId, String inputId,
584                     Uri channelUri, long startTime, long duration, int repeatDays, Bundle params,
585                     int seq) {
586                 synchronized (mSessionCallbackRecordMap) {
587                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
588                     if (record == null) {
589                         Log.e(TAG, "Callback not found for seq " + seq);
590                         return;
591                     }
592                     record.postRequestScheduleRecording(requestId, inputId, channelUri, startTime,
593                             duration, repeatDays, params);
594                 }
595             }
596 
597             @Override
598             public void onSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo,
599                     int seq) {
600                 synchronized (mSessionCallbackRecordMap) {
601                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
602                     if (record == null) {
603                         Log.e(TAG, "Callback not found for seq " + seq);
604                         return;
605                     }
606                     record.postSetTvRecordingInfo(recordingId, recordingInfo);
607                 }
608             }
609 
610             @Override
611             public void onRequestTvRecordingInfo(String recordingId, int seq) {
612                 synchronized (mSessionCallbackRecordMap) {
613                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
614                     if (record == null) {
615                         Log.e(TAG, "Callback not found for seq " + seq);
616                         return;
617                     }
618                     record.postRequestTvRecordingInfo(recordingId);
619                 }
620             }
621 
622             @Override
623             public void onRequestTvRecordingInfoList(int type, int seq) {
624                 synchronized (mSessionCallbackRecordMap) {
625                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
626                     if (record == null) {
627                         Log.e(TAG, "Callback not found for seq " + seq);
628                         return;
629                     }
630                     record.postRequestTvRecordingInfoList(type);
631                 }
632             }
633 
634             @Override
635             public void onRequestSigning(
636                     String id, String algorithm, String alias, byte[] data, int seq) {
637                 synchronized (mSessionCallbackRecordMap) {
638                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
639                     if (record == null) {
640                         Log.e(TAG, "Callback not found for seq " + seq);
641                         return;
642                     }
643                     record.postRequestSigning(id, algorithm, alias, data);
644                 }
645             }
646 
647             @Override
648             public void onSessionStateChanged(int state, int err, int seq) {
649                 synchronized (mSessionCallbackRecordMap) {
650                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
651                     if (record == null) {
652                         Log.e(TAG, "Callback not found for seq " + seq);
653                         return;
654                     }
655                     record.postSessionStateChanged(state, err);
656                 }
657             }
658 
659             @Override
660             public void onBiInteractiveAppCreated(Uri biIAppUri, String biIAppId, int seq) {
661                 synchronized (mSessionCallbackRecordMap) {
662                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
663                     if (record == null) {
664                         Log.e(TAG, "Callback not found for seq " + seq);
665                         return;
666                     }
667                     record.postBiInteractiveAppCreated(biIAppUri, biIAppId);
668                 }
669             }
670 
671             @Override
672             public void onTeletextAppStateChanged(int state, int seq) {
673                 synchronized (mSessionCallbackRecordMap) {
674                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
675                     if (record == null) {
676                         Log.e(TAG, "Callback not found for seq " + seq);
677                         return;
678                     }
679                     record.postTeletextAppStateChanged(state);
680                 }
681             }
682 
683             @Override
684             public void onAdBufferReady(AdBuffer buffer, int seq) {
685                 synchronized (mSessionCallbackRecordMap) {
686                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
687                     if (record == null) {
688                         Log.e(TAG, "Callback not found for seq " + seq);
689                         return;
690                     }
691                     record.postAdBufferReady(buffer);
692                 }
693             }
694         };
695         ITvInteractiveAppManagerCallback managerCallback =
696                 new ITvInteractiveAppManagerCallback.Stub() {
697             @Override
698             public void onInteractiveAppServiceAdded(String iAppServiceId) {
699                 synchronized (mLock) {
700                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
701                         record.postInteractiveAppServiceAdded(iAppServiceId);
702                     }
703                 }
704             }
705 
706             @Override
707             public void onInteractiveAppServiceRemoved(String iAppServiceId) {
708                 synchronized (mLock) {
709                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
710                         record.postInteractiveAppServiceRemoved(iAppServiceId);
711                     }
712                 }
713             }
714 
715             @Override
716             public void onInteractiveAppServiceUpdated(String iAppServiceId) {
717                 synchronized (mLock) {
718                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
719                         record.postInteractiveAppServiceUpdated(iAppServiceId);
720                     }
721                 }
722             }
723 
724             @Override
725             public void onTvInteractiveAppServiceInfoUpdated(TvInteractiveAppServiceInfo iAppInfo) {
726                 // TODO: add public API updateInteractiveAppInfo()
727                 synchronized (mLock) {
728                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
729                         record.postTvInteractiveAppServiceInfoUpdated(iAppInfo);
730                     }
731                 }
732             }
733 
734             @Override
735             public void onStateChanged(String iAppServiceId, int type, int state, int err) {
736                 synchronized (mLock) {
737                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
738                         record.postStateChanged(iAppServiceId, type, state, err);
739                     }
740                 }
741             }
742         };
743         try {
744             if (mService != null) {
745                 mService.registerCallback(managerCallback, mUserId);
746             }
747         } catch (RemoteException e) {
748             throw e.rethrowFromSystemServer();
749         }
750     }
751 
752     /**
753      * Callback used to monitor status of the TV Interactive App.
754      */
755     public abstract static class TvInteractiveAppCallback {
756         /**
757          * This is called when a TV Interactive App service is added to the system.
758          *
759          * <p>Normally it happens when the user installs a new TV Interactive App service package
760          * that implements {@link TvInteractiveAppService} interface.
761          *
762          * @param iAppServiceId The ID of the TV Interactive App service.
763          */
onInteractiveAppServiceAdded(@onNull String iAppServiceId)764         public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
765         }
766 
767         /**
768          * This is called when a TV Interactive App service is removed from the system.
769          *
770          * <p>Normally it happens when the user uninstalls the previously installed TV Interactive
771          * App service package.
772          *
773          * @param iAppServiceId The ID of the TV Interactive App service.
774          */
onInteractiveAppServiceRemoved(@onNull String iAppServiceId)775         public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
776         }
777 
778         /**
779          * This is called when a TV Interactive App service is updated on the system.
780          *
781          * <p>Normally it happens when a previously installed TV Interactive App service package is
782          * re-installed or a newer version of the package exists becomes available/unavailable.
783          *
784          * @param iAppServiceId The ID of the TV Interactive App service.
785          */
onInteractiveAppServiceUpdated(@onNull String iAppServiceId)786         public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) {
787         }
788 
789         /**
790          * This is called when the information about an existing TV Interactive App service has been
791          * updated.
792          *
793          * <p>Because the system automatically creates a <code>TvInteractiveAppServiceInfo</code>
794          * object for each TV Interactive App service based on the information collected from the
795          * <code>AndroidManifest.xml</code>, this method is only called back when such information
796          * has changed dynamically.
797          *
798          * @param iAppInfo The <code>TvInteractiveAppServiceInfo</code> object that contains new
799          *                 information.
800          * @hide
801          */
onTvInteractiveAppServiceInfoUpdated( @onNull TvInteractiveAppServiceInfo iAppInfo)802         public void onTvInteractiveAppServiceInfoUpdated(
803                 @NonNull TvInteractiveAppServiceInfo iAppInfo) {
804         }
805 
806         /**
807          * This is called when the state of the interactive app service is changed.
808          *
809          * @param iAppServiceId The ID of the TV Interactive App service.
810          * @param type the interactive app type
811          * @param state the current state of the service of the given type
812          * @param err the error code for error state. {@link #ERROR_NONE} is used when the state is
813          *            not {@link #SERVICE_STATE_ERROR}.
814          */
onTvInteractiveAppServiceStateChanged( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type, @ServiceState int state, @ErrorCode int err)815         public void onTvInteractiveAppServiceStateChanged(
816                 @NonNull String iAppServiceId,
817                 @TvInteractiveAppServiceInfo.InteractiveAppType int type,
818                 @ServiceState int state,
819                 @ErrorCode int err) {
820         }
821     }
822 
823     private static final class TvInteractiveAppCallbackRecord {
824         private final TvInteractiveAppCallback mCallback;
825         private final Executor mExecutor;
826 
TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor)827         TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor) {
828             mCallback = callback;
829             mExecutor = executor;
830         }
831 
getCallback()832         public TvInteractiveAppCallback getCallback() {
833             return mCallback;
834         }
835 
postInteractiveAppServiceAdded(final String iAppServiceId)836         public void postInteractiveAppServiceAdded(final String iAppServiceId) {
837             mExecutor.execute(new Runnable() {
838                 @Override
839                 public void run() {
840                     mCallback.onInteractiveAppServiceAdded(iAppServiceId);
841                 }
842             });
843         }
844 
postInteractiveAppServiceRemoved(final String iAppServiceId)845         public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
846             mExecutor.execute(new Runnable() {
847                 @Override
848                 public void run() {
849                     mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
850                 }
851             });
852         }
853 
postInteractiveAppServiceUpdated(final String iAppServiceId)854         public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
855             mExecutor.execute(new Runnable() {
856                 @Override
857                 public void run() {
858                     mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
859                 }
860             });
861         }
862 
postTvInteractiveAppServiceInfoUpdated( final TvInteractiveAppServiceInfo iAppInfo)863         public void postTvInteractiveAppServiceInfoUpdated(
864                 final TvInteractiveAppServiceInfo iAppInfo) {
865             mExecutor.execute(new Runnable() {
866                 @Override
867                 public void run() {
868                     mCallback.onTvInteractiveAppServiceInfoUpdated(iAppInfo);
869                 }
870             });
871         }
872 
postStateChanged(String iAppServiceId, int type, int state, int err)873         public void postStateChanged(String iAppServiceId, int type, int state, int err) {
874             mExecutor.execute(new Runnable() {
875                 @Override
876                 public void run() {
877                     mCallback.onTvInteractiveAppServiceStateChanged(
878                             iAppServiceId, type, state, err);
879                 }
880             });
881         }
882     }
883 
884     /**
885      * Creates a {@link Session} for a given TV interactive application.
886      *
887      * <p>The number of sessions that can be created at the same time is limited by the capability
888      * of the given interactive application.
889      *
890      * @param iAppServiceId The ID of the interactive application.
891      * @param type the type of the interactive application.
892      * @param callback A callback used to receive the created session.
893      * @param handler A {@link Handler} that the session creation will be delivered to.
894      * @hide
895      */
createSession(@onNull String iAppServiceId, int type, @NonNull final SessionCallback callback, @NonNull Handler handler)896     public void createSession(@NonNull String iAppServiceId, int type,
897             @NonNull final SessionCallback callback, @NonNull Handler handler) {
898         createSessionInternal(iAppServiceId, type, callback, handler);
899     }
900 
createSessionInternal(String iAppServiceId, int type, SessionCallback callback, Handler handler)901     private void createSessionInternal(String iAppServiceId, int type, SessionCallback callback,
902             Handler handler) {
903         Preconditions.checkNotNull(iAppServiceId);
904         Preconditions.checkNotNull(callback);
905         Preconditions.checkNotNull(handler);
906         SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
907         synchronized (mSessionCallbackRecordMap) {
908             int seq = mNextSeq++;
909             mSessionCallbackRecordMap.put(seq, record);
910             try {
911                 mService.createSession(mClient, iAppServiceId, type, seq, mUserId);
912             } catch (RemoteException e) {
913                 throw e.rethrowFromSystemServer();
914             }
915         }
916     }
917 
918     /**
919      * Returns the complete list of TV Interactive App service on the system.
920      *
921      * @return List of {@link TvInteractiveAppServiceInfo} for each TV Interactive App service that
922      *         describes its meta information.
923      */
924     @NonNull
getTvInteractiveAppServiceList()925     public List<TvInteractiveAppServiceInfo> getTvInteractiveAppServiceList() {
926         try {
927             return mService.getTvInteractiveAppServiceList(mUserId);
928         } catch (RemoteException e) {
929             throw e.rethrowFromSystemServer();
930         }
931     }
932 
933     /**
934      * Returns a list of available app link information.
935      *
936      * <P>A package must declare its app link info in its manifest using meta-data tag, so the info
937      * can be detected by the system.
938      *
939      * @return List of {@link AppLinkInfo} for each package that deslares its app link information.
940      */
941     @NonNull
getAppLinkInfoList()942     public List<AppLinkInfo> getAppLinkInfoList() {
943         try {
944             return mService.getAppLinkInfoList(mUserId);
945         } catch (RemoteException e) {
946             throw e.rethrowFromSystemServer();
947         }
948     }
949 
950     /**
951      * Registers an Android application link info record which can be used to launch the specific
952      * Android application by TV interactive App RTE.
953      *
954      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
955      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
956      * @param appLinkInfo The Android application link info record to be registered.
957      */
registerAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)958     public void registerAppLinkInfo(
959             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
960         try {
961             mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
962         } catch (RemoteException e) {
963             throw e.rethrowFromSystemServer();
964         }
965     }
966 
967     /**
968      * Unregisters an Android application link info record which can be used to launch the specific
969      * Android application by TV interactive App RTE.
970      *
971      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
972      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
973      * @param appLinkInfo The Android application link info record to be unregistered.
974      */
unregisterAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)975     public void unregisterAppLinkInfo(
976             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
977         try {
978             mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
979         } catch (RemoteException e) {
980             throw e.rethrowFromSystemServer();
981         }
982     }
983 
984     /**
985      * Sends app link command.
986      *
987      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
988      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
989      * @param command The command to be sent.
990      */
sendAppLinkCommand(@onNull String tvIAppServiceId, @NonNull Bundle command)991     public void sendAppLinkCommand(@NonNull String tvIAppServiceId, @NonNull Bundle command) {
992         try {
993             mService.sendAppLinkCommand(tvIAppServiceId, command, mUserId);
994         } catch (RemoteException e) {
995             throw e.rethrowFromSystemServer();
996         }
997     }
998 
999     /**
1000      * Registers a {@link TvInteractiveAppCallback}.
1001      *
1002      * @param callback A callback used to monitor status of the TV Interactive App services.
1003      * @param executor A {@link Executor} that the status change will be delivered to.
1004      */
registerCallback( @allbackExecutor @onNull Executor executor, @NonNull TvInteractiveAppCallback callback)1005     public void registerCallback(
1006             @CallbackExecutor @NonNull Executor executor,
1007             @NonNull TvInteractiveAppCallback callback) {
1008         Preconditions.checkNotNull(callback);
1009         Preconditions.checkNotNull(executor);
1010         synchronized (mLock) {
1011             mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, executor));
1012         }
1013     }
1014 
1015     /**
1016      * Unregisters the existing {@link TvInteractiveAppCallback}.
1017      *
1018      * @param callback The existing callback to remove.
1019      */
unregisterCallback(@onNull final TvInteractiveAppCallback callback)1020     public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
1021         Preconditions.checkNotNull(callback);
1022         synchronized (mLock) {
1023             for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator();
1024                     it.hasNext(); ) {
1025                 TvInteractiveAppCallbackRecord record = it.next();
1026                 if (record.getCallback() == callback) {
1027                     it.remove();
1028                     break;
1029                 }
1030             }
1031         }
1032     }
1033 
1034     /**
1035      * The Session provides the per-session functionality of interactive app.
1036      * @hide
1037      */
1038     public static final class Session {
1039         static final int DISPATCH_IN_PROGRESS = -1;
1040         static final int DISPATCH_NOT_HANDLED = 0;
1041         static final int DISPATCH_HANDLED = 1;
1042 
1043         private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
1044 
1045         private final ITvInteractiveAppManager mService;
1046         private final int mUserId;
1047         private final int mSeq;
1048         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
1049 
1050         // For scheduling input event handling on the main thread. This also serves as a lock to
1051         // protect pending input events and the input channel.
1052         private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
1053 
1054         private TvInputManager.Session mInputSession;
1055         private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
1056         private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
1057 
1058         private IBinder mToken;
1059         private TvInputEventSender mSender;
1060         private InputChannel mInputChannel;
1061 
Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap)1062         private Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service,
1063                 int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
1064             mToken = token;
1065             mInputChannel = channel;
1066             mService = service;
1067             mUserId = userId;
1068             mSeq = seq;
1069             mSessionCallbackRecordMap = sessionCallbackRecordMap;
1070         }
1071 
getInputSession()1072         public TvInputManager.Session getInputSession() {
1073             return mInputSession;
1074         }
1075 
setInputSession(TvInputManager.Session inputSession)1076         public void setInputSession(TvInputManager.Session inputSession) {
1077             mInputSession = inputSession;
1078         }
1079 
startInteractiveApp()1080         void startInteractiveApp() {
1081             if (mToken == null) {
1082                 Log.w(TAG, "The session has been already released");
1083                 return;
1084             }
1085             try {
1086                 mService.startInteractiveApp(mToken, mUserId);
1087             } catch (RemoteException e) {
1088                 throw e.rethrowFromSystemServer();
1089             }
1090         }
1091 
stopInteractiveApp()1092         void stopInteractiveApp() {
1093             if (mToken == null) {
1094                 Log.w(TAG, "The session has been already released");
1095                 return;
1096             }
1097             try {
1098                 mService.stopInteractiveApp(mToken, mUserId);
1099             } catch (RemoteException e) {
1100                 throw e.rethrowFromSystemServer();
1101             }
1102         }
1103 
resetInteractiveApp()1104         void resetInteractiveApp() {
1105             if (mToken == null) {
1106                 Log.w(TAG, "The session has been already released");
1107                 return;
1108             }
1109             try {
1110                 mService.resetInteractiveApp(mToken, mUserId);
1111             } catch (RemoteException e) {
1112                 throw e.rethrowFromSystemServer();
1113             }
1114         }
1115 
createBiInteractiveApp(Uri biIAppUri, Bundle params)1116         void createBiInteractiveApp(Uri biIAppUri, Bundle params) {
1117             if (mToken == null) {
1118                 Log.w(TAG, "The session has been already released");
1119                 return;
1120             }
1121             try {
1122                 mService.createBiInteractiveApp(mToken, biIAppUri, params, mUserId);
1123             } catch (RemoteException e) {
1124                 throw e.rethrowFromSystemServer();
1125             }
1126         }
1127 
destroyBiInteractiveApp(String biIAppId)1128         void destroyBiInteractiveApp(String biIAppId) {
1129             if (mToken == null) {
1130                 Log.w(TAG, "The session has been already released");
1131                 return;
1132             }
1133             try {
1134                 mService.destroyBiInteractiveApp(mToken, biIAppId, mUserId);
1135             } catch (RemoteException e) {
1136                 throw e.rethrowFromSystemServer();
1137             }
1138         }
1139 
setTeletextAppEnabled(boolean enable)1140         void setTeletextAppEnabled(boolean enable) {
1141             if (mToken == null) {
1142                 Log.w(TAG, "The session has been already released");
1143                 return;
1144             }
1145             try {
1146                 mService.setTeletextAppEnabled(mToken, enable, mUserId);
1147             } catch (RemoteException e) {
1148                 throw e.rethrowFromSystemServer();
1149             }
1150         }
1151 
sendCurrentVideoBounds(@onNull Rect bounds)1152         void sendCurrentVideoBounds(@NonNull Rect bounds) {
1153             if (mToken == null) {
1154                 Log.w(TAG, "The session has been already released");
1155                 return;
1156             }
1157             try {
1158                 mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
1159             } catch (RemoteException e) {
1160                 throw e.rethrowFromSystemServer();
1161             }
1162         }
1163 
sendCurrentChannelUri(@ullable Uri channelUri)1164         void sendCurrentChannelUri(@Nullable Uri channelUri) {
1165             if (mToken == null) {
1166                 Log.w(TAG, "The session has been already released");
1167                 return;
1168             }
1169             try {
1170                 mService.sendCurrentChannelUri(mToken, channelUri, mUserId);
1171             } catch (RemoteException e) {
1172                 throw e.rethrowFromSystemServer();
1173             }
1174         }
1175 
sendCurrentChannelLcn(int lcn)1176         void sendCurrentChannelLcn(int lcn) {
1177             if (mToken == null) {
1178                 Log.w(TAG, "The session has been already released");
1179                 return;
1180             }
1181             try {
1182                 mService.sendCurrentChannelLcn(mToken, lcn, mUserId);
1183             } catch (RemoteException e) {
1184                 throw e.rethrowFromSystemServer();
1185             }
1186         }
1187 
sendStreamVolume(float volume)1188         void sendStreamVolume(float volume) {
1189             if (mToken == null) {
1190                 Log.w(TAG, "The session has been already released");
1191                 return;
1192             }
1193             try {
1194                 mService.sendStreamVolume(mToken, volume, mUserId);
1195             } catch (RemoteException e) {
1196                 throw e.rethrowFromSystemServer();
1197             }
1198         }
1199 
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1200         void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
1201             if (mToken == null) {
1202                 Log.w(TAG, "The session has been already released");
1203                 return;
1204             }
1205             try {
1206                 mService.sendTrackInfoList(mToken, tracks, mUserId);
1207             } catch (RemoteException e) {
1208                 throw e.rethrowFromSystemServer();
1209             }
1210         }
1211 
sendCurrentTvInputId(@ullable String inputId)1212         void sendCurrentTvInputId(@Nullable String inputId) {
1213             if (mToken == null) {
1214                 Log.w(TAG, "The session has been already released");
1215                 return;
1216             }
1217             try {
1218                 mService.sendCurrentTvInputId(mToken, inputId, mUserId);
1219             } catch (RemoteException e) {
1220                 throw e.rethrowFromSystemServer();
1221             }
1222         }
1223 
sendTimeShiftMode(int mode)1224         void sendTimeShiftMode(int mode) {
1225             if (mToken == null) {
1226                 Log.w(TAG, "The session has been already released");
1227                 return;
1228             }
1229             try {
1230                 mService.sendTimeShiftMode(mToken, mode, mUserId);
1231             } catch (RemoteException e) {
1232                 throw e.rethrowFromSystemServer();
1233             }
1234         }
1235 
sendAvailableSpeeds(float[] speeds)1236         void sendAvailableSpeeds(float[] speeds) {
1237             if (mToken == null) {
1238                 Log.w(TAG, "The session has been already released");
1239                 return;
1240             }
1241             try {
1242                 mService.sendAvailableSpeeds(mToken, speeds, mUserId);
1243             } catch (RemoteException e) {
1244                 throw e.rethrowFromSystemServer();
1245             }
1246         }
1247 
sendTvRecordingInfo(@ullable TvRecordingInfo recordingInfo)1248         void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
1249             if (mToken == null) {
1250                 Log.w(TAG, "The session has been already released");
1251                 return;
1252             }
1253             try {
1254                 mService.sendTvRecordingInfo(mToken, recordingInfo, mUserId);
1255             } catch (RemoteException e) {
1256                 throw e.rethrowFromSystemServer();
1257             }
1258         }
1259 
sendTvRecordingInfoList(@ullable List<TvRecordingInfo> recordingInfoList)1260         void sendTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
1261             if (mToken == null) {
1262                 Log.w(TAG, "The session has been already released");
1263                 return;
1264             }
1265             try {
1266                 mService.sendTvRecordingInfoList(mToken, recordingInfoList, mUserId);
1267             } catch (RemoteException e) {
1268                 throw e.rethrowFromSystemServer();
1269             }
1270         }
1271 
notifyRecordingStarted(String recordingId, String requestId)1272         void notifyRecordingStarted(String recordingId, String requestId) {
1273             if (mToken == null) {
1274                 Log.w(TAG, "The session has been already released");
1275                 return;
1276             }
1277             try {
1278                 mService.notifyRecordingStarted(mToken, recordingId, requestId, mUserId);
1279             } catch (RemoteException e) {
1280                 throw e.rethrowFromSystemServer();
1281             }
1282         }
1283 
notifyRecordingStopped(String recordingId)1284         void notifyRecordingStopped(String recordingId) {
1285             if (mToken == null) {
1286                 Log.w(TAG, "The session has been already released");
1287                 return;
1288             }
1289             try {
1290                 mService.notifyRecordingStopped(mToken, recordingId, mUserId);
1291             } catch (RemoteException e) {
1292                 throw e.rethrowFromSystemServer();
1293             }
1294         }
1295 
sendSigningResult(@onNull String signingId, @NonNull byte[] result)1296         void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
1297             if (mToken == null) {
1298                 Log.w(TAG, "The session has been already released");
1299                 return;
1300             }
1301             try {
1302                 mService.sendSigningResult(mToken, signingId, result, mUserId);
1303             } catch (RemoteException e) {
1304                 throw e.rethrowFromSystemServer();
1305             }
1306         }
1307 
notifyError(@onNull String errMsg, @NonNull Bundle params)1308         void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
1309             if (mToken == null) {
1310                 Log.w(TAG, "The session has been already released");
1311                 return;
1312             }
1313             try {
1314                 mService.notifyError(mToken, errMsg, params, mUserId);
1315             } catch (RemoteException e) {
1316                 throw e.rethrowFromSystemServer();
1317             }
1318         }
1319 
notifyTimeShiftPlaybackParams(@onNull PlaybackParams params)1320         void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
1321             if (mToken == null) {
1322                 Log.w(TAG, "The session has been already released");
1323                 return;
1324             }
1325             try {
1326                 mService.notifyTimeShiftPlaybackParams(mToken, params, mUserId);
1327             } catch (RemoteException e) {
1328                 throw e.rethrowFromSystemServer();
1329             }
1330         }
1331 
notifyTimeShiftStatusChanged( @onNull String inputId, @TvInputManager.TimeShiftStatus int status)1332         void notifyTimeShiftStatusChanged(
1333                 @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
1334             if (mToken == null) {
1335                 Log.w(TAG, "The session has been already released");
1336                 return;
1337             }
1338             try {
1339                 mService.notifyTimeShiftStatusChanged(mToken, inputId, status, mUserId);
1340             } catch (RemoteException e) {
1341                 throw e.rethrowFromSystemServer();
1342             }
1343         }
1344 
notifyTimeShiftStartPositionChanged(@onNull String inputId, long timeMs)1345         void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
1346             if (mToken == null) {
1347                 Log.w(TAG, "The session has been already released");
1348                 return;
1349             }
1350             try {
1351                 mService.notifyTimeShiftStartPositionChanged(mToken, inputId, timeMs, mUserId);
1352             } catch (RemoteException e) {
1353                 throw e.rethrowFromSystemServer();
1354             }
1355         }
1356 
notifyTimeShiftCurrentPositionChanged(@onNull String inputId, long timeMs)1357         void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
1358             if (mToken == null) {
1359                 Log.w(TAG, "The session has been already released");
1360                 return;
1361             }
1362             try {
1363                 mService.notifyTimeShiftCurrentPositionChanged(mToken, inputId, timeMs, mUserId);
1364             } catch (RemoteException e) {
1365                 throw e.rethrowFromSystemServer();
1366             }
1367         }
1368 
notifyRecordingConnectionFailed(@onNull String recordingId, @NonNull String inputId)1369         void notifyRecordingConnectionFailed(@NonNull String recordingId, @NonNull String inputId) {
1370             if (mToken == null) {
1371                 Log.w(TAG, "The session has been already released");
1372                 return;
1373             }
1374             try {
1375                 mService.notifyRecordingConnectionFailed(mToken, recordingId, inputId, mUserId);
1376             } catch (RemoteException e) {
1377                 throw e.rethrowFromSystemServer();
1378             }
1379         }
1380 
notifyRecordingDisconnected(@onNull String recordingId, @NonNull String inputId)1381         void notifyRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) {
1382             if (mToken == null) {
1383                 Log.w(TAG, "The session has been already released");
1384                 return;
1385             }
1386             try {
1387                 mService.notifyRecordingDisconnected(mToken, recordingId, inputId, mUserId);
1388             } catch (RemoteException e) {
1389                 throw e.rethrowFromSystemServer();
1390             }
1391         }
1392 
notifyRecordingTuned(@onNull String recordingId, @NonNull Uri channelUri)1393         void notifyRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) {
1394             if (mToken == null) {
1395                 Log.w(TAG, "The session has been already released");
1396                 return;
1397             }
1398             try {
1399                 mService.notifyRecordingTuned(mToken, recordingId, channelUri, mUserId);
1400             } catch (RemoteException e) {
1401                 throw e.rethrowFromSystemServer();
1402             }
1403         }
1404 
notifyRecordingError(@onNull String recordingId, int err)1405         void notifyRecordingError(@NonNull String recordingId, int err) {
1406             if (mToken == null) {
1407                 Log.w(TAG, "The session has been already released");
1408                 return;
1409             }
1410             try {
1411                 mService.notifyRecordingError(mToken, recordingId, err, mUserId);
1412             } catch (RemoteException e) {
1413                 throw e.rethrowFromSystemServer();
1414             }
1415         }
1416 
notifyRecordingScheduled(@onNull String recordingId, @Nullable String requestId)1417         void notifyRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) {
1418             if (mToken == null) {
1419                 Log.w(TAG, "The session has been already released");
1420                 return;
1421             }
1422             try {
1423                 mService.notifyRecordingScheduled(mToken, recordingId, requestId, mUserId);
1424             } catch (RemoteException e) {
1425                 throw e.rethrowFromSystemServer();
1426             }
1427         }
1428 
1429         /**
1430          * Sets the {@link android.view.Surface} for this session.
1431          *
1432          * @param surface A {@link android.view.Surface} used to render video.
1433          */
setSurface(Surface surface)1434         public void setSurface(Surface surface) {
1435             if (mToken == null) {
1436                 Log.w(TAG, "The session has been already released");
1437                 return;
1438             }
1439             // surface can be null.
1440             try {
1441                 mService.setSurface(mToken, surface, mUserId);
1442             } catch (RemoteException e) {
1443                 throw e.rethrowFromSystemServer();
1444             }
1445         }
1446 
1447         /**
1448          * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
1449          * should be called whenever the layout of its containing view is changed.
1450          * {@link #removeMediaView()} should be called to remove the media view.
1451          * Since a session can have only one media view, this method should be called only once
1452          * or it can be called again after calling {@link #removeMediaView()}.
1453          *
1454          * @param view A view for interactive app.
1455          * @param frame A position of the media view.
1456          * @throws IllegalStateException if {@code view} is not attached to a window.
1457          */
createMediaView(@onNull View view, @NonNull Rect frame)1458         void createMediaView(@NonNull View view, @NonNull Rect frame) {
1459             Preconditions.checkNotNull(view);
1460             Preconditions.checkNotNull(frame);
1461             if (view.getWindowToken() == null) {
1462                 throw new IllegalStateException("view must be attached to a window");
1463             }
1464             if (mToken == null) {
1465                 Log.w(TAG, "The session has been already released");
1466                 return;
1467             }
1468             try {
1469                 mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
1470             } catch (RemoteException e) {
1471                 throw e.rethrowFromSystemServer();
1472             }
1473         }
1474 
1475         /**
1476          * Relayouts the current media view.
1477          *
1478          * @param frame A new position of the media view.
1479          */
relayoutMediaView(@onNull Rect frame)1480         void relayoutMediaView(@NonNull Rect frame) {
1481             Preconditions.checkNotNull(frame);
1482             if (mToken == null) {
1483                 Log.w(TAG, "The session has been already released");
1484                 return;
1485             }
1486             try {
1487                 mService.relayoutMediaView(mToken, frame, mUserId);
1488             } catch (RemoteException e) {
1489                 throw e.rethrowFromSystemServer();
1490             }
1491         }
1492 
1493         /**
1494          * Removes the current media view.
1495          */
removeMediaView()1496         void removeMediaView() {
1497             if (mToken == null) {
1498                 Log.w(TAG, "The session has been already released");
1499                 return;
1500             }
1501             try {
1502                 mService.removeMediaView(mToken, mUserId);
1503             } catch (RemoteException e) {
1504                 throw e.rethrowFromSystemServer();
1505             }
1506         }
1507 
1508         /**
1509          * Notifies of any structural changes (format or size) of the surface passed in
1510          * {@link #setSurface}.
1511          *
1512          * @param format The new PixelFormat of the surface.
1513          * @param width The new width of the surface.
1514          * @param height The new height of the surface.
1515          */
dispatchSurfaceChanged(int format, int width, int height)1516         public void dispatchSurfaceChanged(int format, int width, int height) {
1517             if (mToken == null) {
1518                 Log.w(TAG, "The session has been already released");
1519                 return;
1520             }
1521             try {
1522                 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
1523             } catch (RemoteException e) {
1524                 throw e.rethrowFromSystemServer();
1525             }
1526         }
1527 
1528         /**
1529          * Dispatches an input event to this session.
1530          *
1531          * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
1532          * @param token A token used to identify the input event later in the callback.
1533          * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
1534          * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
1535          *            {@code null}.
1536          * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
1537          *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
1538          *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
1539          *         be invoked later.
1540          * @hide
1541          */
dispatchInputEvent(@onNull InputEvent event, Object token, @NonNull FinishedInputEventCallback callback, @NonNull Handler handler)1542         public int dispatchInputEvent(@NonNull InputEvent event, Object token,
1543                 @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
1544             Preconditions.checkNotNull(event);
1545             Preconditions.checkNotNull(callback);
1546             Preconditions.checkNotNull(handler);
1547             synchronized (mHandler) {
1548                 if (mInputChannel == null) {
1549                     return DISPATCH_NOT_HANDLED;
1550                 }
1551                 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
1552                 if (Looper.myLooper() == Looper.getMainLooper()) {
1553                     // Already running on the main thread so we can send the event immediately.
1554                     return sendInputEventOnMainLooperLocked(p);
1555                 }
1556 
1557                 // Post the event to the main thread.
1558                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
1559                 msg.setAsynchronous(true);
1560                 mHandler.sendMessage(msg);
1561                 return DISPATCH_IN_PROGRESS;
1562             }
1563         }
1564 
1565         /**
1566          * Notifies of any broadcast info response passed in from TIS.
1567          *
1568          * @param response response passed in from TIS.
1569          */
notifyBroadcastInfoResponse(BroadcastInfoResponse response)1570         public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
1571             if (mToken == null) {
1572                 Log.w(TAG, "The session has been already released");
1573                 return;
1574             }
1575             try {
1576                 mService.notifyBroadcastInfoResponse(mToken, response, mUserId);
1577             } catch (RemoteException e) {
1578                 throw e.rethrowFromSystemServer();
1579             }
1580         }
1581 
1582         /**
1583          * Notifies of any advertisement response passed in from TIS.
1584          *
1585          * @param response response passed in from TIS.
1586          */
notifyAdResponse(AdResponse response)1587         public void notifyAdResponse(AdResponse response) {
1588             if (mToken == null) {
1589                 Log.w(TAG, "The session has been already released");
1590                 return;
1591             }
1592             try {
1593                 mService.notifyAdResponse(mToken, response, mUserId);
1594             } catch (RemoteException e) {
1595                 throw e.rethrowFromSystemServer();
1596             }
1597         }
1598 
1599         /**
1600          * Notifies the advertisement buffer is consumed.
1601          */
notifyAdBufferConsumed(AdBuffer buffer)1602         public void notifyAdBufferConsumed(AdBuffer buffer) {
1603             if (mToken == null) {
1604                 Log.w(TAG, "The session has been already released");
1605                 return;
1606             }
1607             try {
1608                 mService.notifyAdBufferConsumed(mToken, buffer, mUserId);
1609             } catch (RemoteException e) {
1610                 throw e.rethrowFromSystemServer();
1611             } finally {
1612                 if (buffer != null) {
1613                     buffer.getSharedMemory().close();
1614                 }
1615             }
1616         }
1617 
1618         /**
1619          * Releases this session.
1620          */
release()1621         public void release() {
1622             if (mToken == null) {
1623                 Log.w(TAG, "The session has been already released");
1624                 return;
1625             }
1626             try {
1627                 mService.releaseSession(mToken, mUserId);
1628             } catch (RemoteException e) {
1629                 throw e.rethrowFromSystemServer();
1630             }
1631 
1632             releaseInternal();
1633         }
1634 
1635         /**
1636          * Notifies Interactive APP session when a channel is tuned.
1637          */
notifyTuned(Uri channelUri)1638         public void notifyTuned(Uri channelUri) {
1639             if (mToken == null) {
1640                 Log.w(TAG, "The session has been already released");
1641                 return;
1642             }
1643             try {
1644                 mService.notifyTuned(mToken, channelUri, mUserId);
1645             } catch (RemoteException e) {
1646                 throw e.rethrowFromSystemServer();
1647             }
1648         }
1649 
1650         /**
1651          * Notifies Interactive APP session when a track is selected.
1652          */
notifyTrackSelected(int type, String trackId)1653         public void notifyTrackSelected(int type, String trackId) {
1654             if (mToken == null) {
1655                 Log.w(TAG, "The session has been already released");
1656                 return;
1657             }
1658             try {
1659                 mService.notifyTrackSelected(mToken, type, trackId, mUserId);
1660             } catch (RemoteException e) {
1661                 throw e.rethrowFromSystemServer();
1662             }
1663         }
1664 
1665         /**
1666          * Notifies Interactive APP session when tracks are changed.
1667          */
notifyTracksChanged(List<TvTrackInfo> tracks)1668         public void notifyTracksChanged(List<TvTrackInfo> tracks) {
1669             if (mToken == null) {
1670                 Log.w(TAG, "The session has been already released");
1671                 return;
1672             }
1673             try {
1674                 mService.notifyTracksChanged(mToken, tracks, mUserId);
1675             } catch (RemoteException e) {
1676                 throw e.rethrowFromSystemServer();
1677             }
1678         }
1679 
1680         /**
1681          * Notifies Interactive APP session when video is available.
1682          */
notifyVideoAvailable()1683         public void notifyVideoAvailable() {
1684             if (mToken == null) {
1685                 Log.w(TAG, "The session has been already released");
1686                 return;
1687             }
1688             try {
1689                 mService.notifyVideoAvailable(mToken, mUserId);
1690             } catch (RemoteException e) {
1691                 throw e.rethrowFromSystemServer();
1692             }
1693         }
1694 
1695         /**
1696          * Notifies Interactive APP session when video is unavailable.
1697          */
notifyVideoUnavailable(int reason)1698         public void notifyVideoUnavailable(int reason) {
1699             if (mToken == null) {
1700                 Log.w(TAG, "The session has been already released");
1701                 return;
1702             }
1703             try {
1704                 mService.notifyVideoUnavailable(mToken, reason, mUserId);
1705             } catch (RemoteException e) {
1706                 throw e.rethrowFromSystemServer();
1707             }
1708         }
1709 
1710         /**
1711          * Notifies Interactive APP session when content is allowed.
1712          */
notifyContentAllowed()1713         public void notifyContentAllowed() {
1714             if (mToken == null) {
1715                 Log.w(TAG, "The session has been already released");
1716                 return;
1717             }
1718             try {
1719                 mService.notifyContentAllowed(mToken, mUserId);
1720             } catch (RemoteException e) {
1721                 throw e.rethrowFromSystemServer();
1722             }
1723         }
1724 
1725         /**
1726          * Notifies Interactive APP session when content is blocked.
1727          */
notifyContentBlocked(TvContentRating rating)1728         public void notifyContentBlocked(TvContentRating rating) {
1729             if (mToken == null) {
1730                 Log.w(TAG, "The session has been already released");
1731                 return;
1732             }
1733             try {
1734                 mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId);
1735             } catch (RemoteException e) {
1736                 throw e.rethrowFromSystemServer();
1737             }
1738         }
1739 
1740         /**
1741          * Notifies Interactive APP session when signal strength is changed.
1742          */
notifySignalStrength(int strength)1743         public void notifySignalStrength(int strength) {
1744             if (mToken == null) {
1745                 Log.w(TAG, "The session has been already released");
1746                 return;
1747             }
1748             try {
1749                 mService.notifySignalStrength(mToken, strength, mUserId);
1750             } catch (RemoteException e) {
1751                 throw e.rethrowFromSystemServer();
1752             }
1753         }
1754 
1755         /**
1756          * Notifies Interactive APP session when a new TV message is received.
1757          */
notifyTvMessage(int type, Bundle data)1758         public void notifyTvMessage(int type, Bundle data) {
1759             if (mToken == null) {
1760                 Log.w(TAG, "The session has been already released");
1761                 return;
1762             }
1763             try {
1764                 mService.notifyTvMessage(mToken, type, data, mUserId);
1765             } catch (RemoteException e) {
1766                 throw e.rethrowFromSystemServer();
1767             }
1768         }
1769 
flushPendingEventsLocked()1770         private void flushPendingEventsLocked() {
1771             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
1772 
1773             final int count = mPendingEvents.size();
1774             for (int i = 0; i < count; i++) {
1775                 int seq = mPendingEvents.keyAt(i);
1776                 Message msg = mHandler.obtainMessage(
1777                         InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
1778                 msg.setAsynchronous(true);
1779                 msg.sendToTarget();
1780             }
1781         }
1782 
releaseInternal()1783         private void releaseInternal() {
1784             mToken = null;
1785             synchronized (mHandler) {
1786                 if (mInputChannel != null) {
1787                     if (mSender != null) {
1788                         flushPendingEventsLocked();
1789                         mSender.dispose();
1790                         mSender = null;
1791                     }
1792                     mInputChannel.dispose();
1793                     mInputChannel = null;
1794                 }
1795             }
1796             synchronized (mSessionCallbackRecordMap) {
1797                 mSessionCallbackRecordMap.delete(mSeq);
1798             }
1799         }
1800 
obtainPendingEventLocked(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler)1801         private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
1802                 FinishedInputEventCallback callback, Handler handler) {
1803             PendingEvent p = mPendingEventPool.acquire();
1804             if (p == null) {
1805                 p = new PendingEvent();
1806             }
1807             p.mEvent = event;
1808             p.mEventToken = token;
1809             p.mCallback = callback;
1810             p.mEventHandler = handler;
1811             return p;
1812         }
1813 
1814         // Assumes the event has already been removed from the queue.
invokeFinishedInputEventCallback(PendingEvent p, boolean handled)1815         void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
1816             p.mHandled = handled;
1817             if (p.mEventHandler.getLooper().isCurrentThread()) {
1818                 // Already running on the callback handler thread so we can send the callback
1819                 // immediately.
1820                 p.run();
1821             } else {
1822                 // Post the event to the callback handler thread.
1823                 // In this case, the callback will be responsible for recycling the event.
1824                 Message msg = Message.obtain(p.mEventHandler, p);
1825                 msg.setAsynchronous(true);
1826                 msg.sendToTarget();
1827             }
1828         }
1829 
1830         // Must be called on the main looper
sendInputEventAndReportResultOnMainLooper(PendingEvent p)1831         private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
1832             synchronized (mHandler) {
1833                 int result = sendInputEventOnMainLooperLocked(p);
1834                 if (result == DISPATCH_IN_PROGRESS) {
1835                     return;
1836                 }
1837             }
1838 
1839             invokeFinishedInputEventCallback(p, false);
1840         }
1841 
sendInputEventOnMainLooperLocked(PendingEvent p)1842         private int sendInputEventOnMainLooperLocked(PendingEvent p) {
1843             if (mInputChannel != null) {
1844                 if (mSender == null) {
1845                     mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
1846                 }
1847 
1848                 final InputEvent event = p.mEvent;
1849                 final int seq = event.getSequenceNumber();
1850                 if (mSender.sendInputEvent(seq, event)) {
1851                     mPendingEvents.put(seq, p);
1852                     Message msg = mHandler.obtainMessage(
1853                             InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1854                     msg.setAsynchronous(true);
1855                     mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
1856                     return DISPATCH_IN_PROGRESS;
1857                 }
1858 
1859                 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
1860                         + event);
1861             }
1862             return DISPATCH_NOT_HANDLED;
1863         }
1864 
finishedInputEvent(int seq, boolean handled, boolean timeout)1865         void finishedInputEvent(int seq, boolean handled, boolean timeout) {
1866             final PendingEvent p;
1867             synchronized (mHandler) {
1868                 int index = mPendingEvents.indexOfKey(seq);
1869                 if (index < 0) {
1870                     return; // spurious, event already finished or timed out
1871                 }
1872 
1873                 p = mPendingEvents.valueAt(index);
1874                 mPendingEvents.removeAt(index);
1875 
1876                 if (timeout) {
1877                     Log.w(TAG, "Timeout waiting for session to handle input event after "
1878                             + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
1879                 } else {
1880                     mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1881                 }
1882             }
1883 
1884             invokeFinishedInputEventCallback(p, handled);
1885         }
1886 
recyclePendingEventLocked(PendingEvent p)1887         private void recyclePendingEventLocked(PendingEvent p) {
1888             p.recycle();
1889             mPendingEventPool.release(p);
1890         }
1891 
1892         /**
1893          * Callback that is invoked when an input event that was dispatched to this session has been
1894          * finished.
1895          *
1896          * @hide
1897          */
1898         public interface FinishedInputEventCallback {
1899             /**
1900              * Called when the dispatched input event is finished.
1901              *
1902              * @param token A token passed to {@link #dispatchInputEvent}.
1903              * @param handled {@code true} if the dispatched input event was handled properly.
1904              *            {@code false} otherwise.
1905              */
onFinishedInputEvent(Object token, boolean handled)1906             void onFinishedInputEvent(Object token, boolean handled);
1907         }
1908 
1909         private final class InputEventHandler extends Handler {
1910             public static final int MSG_SEND_INPUT_EVENT = 1;
1911             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
1912             public static final int MSG_FLUSH_INPUT_EVENT = 3;
1913 
InputEventHandler(Looper looper)1914             InputEventHandler(Looper looper) {
1915                 super(looper, null, true);
1916             }
1917 
1918             @Override
handleMessage(Message msg)1919             public void handleMessage(Message msg) {
1920                 switch (msg.what) {
1921                     case MSG_SEND_INPUT_EVENT: {
1922                         sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
1923                         return;
1924                     }
1925                     case MSG_TIMEOUT_INPUT_EVENT: {
1926                         finishedInputEvent(msg.arg1, false, true);
1927                         return;
1928                     }
1929                     case MSG_FLUSH_INPUT_EVENT: {
1930                         finishedInputEvent(msg.arg1, false, false);
1931                         return;
1932                     }
1933                 }
1934             }
1935         }
1936 
1937         private final class TvInputEventSender extends InputEventSender {
TvInputEventSender(InputChannel inputChannel, Looper looper)1938             TvInputEventSender(InputChannel inputChannel, Looper looper) {
1939                 super(inputChannel, looper);
1940             }
1941 
1942             @Override
onInputEventFinished(int seq, boolean handled)1943             public void onInputEventFinished(int seq, boolean handled) {
1944                 finishedInputEvent(seq, handled, false);
1945             }
1946         }
1947 
1948         private final class PendingEvent implements Runnable {
1949             public InputEvent mEvent;
1950             public Object mEventToken;
1951             public FinishedInputEventCallback mCallback;
1952             public Handler mEventHandler;
1953             public boolean mHandled;
1954 
recycle()1955             public void recycle() {
1956                 mEvent = null;
1957                 mEventToken = null;
1958                 mCallback = null;
1959                 mEventHandler = null;
1960                 mHandled = false;
1961             }
1962 
1963             @Override
run()1964             public void run() {
1965                 mCallback.onFinishedInputEvent(mEventToken, mHandled);
1966 
1967                 synchronized (mEventHandler) {
1968                     recyclePendingEventLocked(this);
1969                 }
1970             }
1971         }
1972     }
1973 
1974     private static final class SessionCallbackRecord {
1975         private final SessionCallback mSessionCallback;
1976         private final Handler mHandler;
1977         private Session mSession;
1978 
SessionCallbackRecord(SessionCallback sessionCallback, Handler handler)1979         SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
1980             mSessionCallback = sessionCallback;
1981             mHandler = handler;
1982         }
1983 
postSessionCreated(final Session session)1984         void postSessionCreated(final Session session) {
1985             mSession = session;
1986             mHandler.post(new Runnable() {
1987                 @Override
1988                 public void run() {
1989                     mSessionCallback.onSessionCreated(session);
1990                 }
1991             });
1992         }
1993 
postSessionReleased()1994         void postSessionReleased() {
1995             mHandler.post(new Runnable() {
1996                 @Override
1997                 public void run() {
1998                     mSessionCallback.onSessionReleased(mSession);
1999                 }
2000             });
2001         }
2002 
postLayoutSurface(final int left, final int top, final int right, final int bottom)2003         void postLayoutSurface(final int left, final int top, final int right,
2004                 final int bottom) {
2005             mHandler.post(new Runnable() {
2006                 @Override
2007                 public void run() {
2008                     mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
2009                 }
2010             });
2011         }
2012 
postBroadcastInfoRequest(final BroadcastInfoRequest request)2013         void postBroadcastInfoRequest(final BroadcastInfoRequest request) {
2014             mHandler.post(new Runnable() {
2015                 @Override
2016                 public void run() {
2017                     if (mSession.getInputSession() != null) {
2018                         mSession.getInputSession().requestBroadcastInfo(request);
2019                     }
2020                 }
2021             });
2022         }
2023 
postRemoveBroadcastInfo(final int requestId)2024         void postRemoveBroadcastInfo(final int requestId) {
2025             mHandler.post(new Runnable() {
2026                 @Override
2027                 public void run() {
2028                     if (mSession.getInputSession() != null) {
2029                         mSession.getInputSession().removeBroadcastInfo(requestId);
2030                     }
2031                 }
2032             });
2033         }
2034 
postCommandRequest( final @TvInteractiveAppService.PlaybackCommandType String cmdType, final Bundle parameters)2035         void postCommandRequest(
2036                 final @TvInteractiveAppService.PlaybackCommandType String cmdType,
2037                 final Bundle parameters) {
2038             mHandler.post(new Runnable() {
2039                 @Override
2040                 public void run() {
2041                     mSessionCallback.onCommandRequest(mSession, cmdType, parameters);
2042                 }
2043             });
2044         }
2045 
postTimeShiftCommandRequest( final @TvInteractiveAppService.TimeShiftCommandType String cmdType, final Bundle parameters)2046         void postTimeShiftCommandRequest(
2047                 final @TvInteractiveAppService.TimeShiftCommandType String cmdType,
2048                 final Bundle parameters) {
2049             mHandler.post(new Runnable() {
2050                 @Override
2051                 public void run() {
2052                     mSessionCallback.onTimeShiftCommandRequest(mSession, cmdType, parameters);
2053                 }
2054             });
2055         }
2056 
postSetVideoBounds(Rect rect)2057         void postSetVideoBounds(Rect rect) {
2058             mHandler.post(new Runnable() {
2059                 @Override
2060                 public void run() {
2061                     mSessionCallback.onSetVideoBounds(mSession, rect);
2062                 }
2063             });
2064         }
2065 
postRequestCurrentVideoBounds()2066         void postRequestCurrentVideoBounds() {
2067             mHandler.post(new Runnable() {
2068                 @Override
2069                 public void run() {
2070                     mSessionCallback.onRequestCurrentVideoBounds(mSession);
2071                 }
2072             });
2073         }
2074 
postRequestCurrentChannelUri()2075         void postRequestCurrentChannelUri() {
2076             mHandler.post(new Runnable() {
2077                 @Override
2078                 public void run() {
2079                     mSessionCallback.onRequestCurrentChannelUri(mSession);
2080                 }
2081             });
2082         }
2083 
postRequestCurrentChannelLcn()2084         void postRequestCurrentChannelLcn() {
2085             mHandler.post(new Runnable() {
2086                 @Override
2087                 public void run() {
2088                     mSessionCallback.onRequestCurrentChannelLcn(mSession);
2089                 }
2090             });
2091         }
2092 
postRequestStreamVolume()2093         void postRequestStreamVolume() {
2094             mHandler.post(new Runnable() {
2095                 @Override
2096                 public void run() {
2097                     mSessionCallback.onRequestStreamVolume(mSession);
2098                 }
2099             });
2100         }
2101 
postRequestTrackInfoList()2102         void postRequestTrackInfoList() {
2103             mHandler.post(new Runnable() {
2104                 @Override
2105                 public void run() {
2106                     mSessionCallback.onRequestTrackInfoList(mSession);
2107                 }
2108             });
2109         }
2110 
postRequestCurrentTvInputId()2111         void postRequestCurrentTvInputId() {
2112             mHandler.post(new Runnable() {
2113                 @Override
2114                 public void run() {
2115                     mSessionCallback.onRequestCurrentTvInputId(mSession);
2116                 }
2117             });
2118         }
2119 
postRequestTimeShiftMode()2120         void postRequestTimeShiftMode() {
2121             mHandler.post(new Runnable() {
2122                 @Override
2123                 public void run() {
2124                     mSessionCallback.onRequestTimeShiftMode(mSession);
2125                 }
2126             });
2127         }
2128 
postRequestAvailableSpeeds()2129         void postRequestAvailableSpeeds() {
2130             mHandler.post(new Runnable() {
2131                 @Override
2132                 public void run() {
2133                     mSessionCallback.onRequestAvailableSpeeds(mSession);
2134                 }
2135             });
2136         }
2137 
postRequestStartRecording(String requestId, Uri programUri)2138         void postRequestStartRecording(String requestId, Uri programUri) {
2139             mHandler.post(new Runnable() {
2140                 @Override
2141                 public void run() {
2142                     mSessionCallback.onRequestStartRecording(mSession, requestId, programUri);
2143                 }
2144             });
2145         }
2146 
postRequestStopRecording(String recordingId)2147         void postRequestStopRecording(String recordingId) {
2148             mHandler.post(new Runnable() {
2149                 @Override
2150                 public void run() {
2151                     mSessionCallback.onRequestStopRecording(mSession, recordingId);
2152                 }
2153             });
2154         }
2155 
postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, Uri programUri, Bundle params)2156         void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
2157                 Uri programUri, Bundle params) {
2158             mHandler.post(new Runnable() {
2159                 @Override
2160                 public void run() {
2161                     mSessionCallback.onRequestScheduleRecording(
2162                             mSession, requestId, inputId, channelUri, programUri, params);
2163                 }
2164             });
2165         }
2166 
postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, long startTime, long duration, int repeatDays, Bundle params)2167         void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
2168                 long startTime, long duration, int repeatDays, Bundle params) {
2169             mHandler.post(new Runnable() {
2170                 @Override
2171                 public void run() {
2172                     mSessionCallback.onRequestScheduleRecording(mSession, requestId, inputId,
2173                             channelUri, startTime, duration, repeatDays, params);
2174                 }
2175             });
2176         }
2177 
postRequestSigning(String id, String algorithm, String alias, byte[] data)2178         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
2179             mHandler.post(new Runnable() {
2180                 @Override
2181                 public void run() {
2182                     mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data);
2183                 }
2184             });
2185         }
2186 
postRequestTvRecordingInfo(String recordingId)2187         void postRequestTvRecordingInfo(String recordingId) {
2188             mHandler.post(new Runnable() {
2189                 @Override
2190                 public void run() {
2191                     mSessionCallback.onRequestTvRecordingInfo(mSession, recordingId);
2192                 }
2193             });
2194         }
2195 
postRequestTvRecordingInfoList(int type)2196         void postRequestTvRecordingInfoList(int type) {
2197             mHandler.post(new Runnable() {
2198                 @Override
2199                 public void run() {
2200                     mSessionCallback.onRequestTvRecordingInfoList(mSession, type);
2201                 }
2202             });
2203         }
2204 
postSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo)2205         void postSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo) {
2206             mHandler.post(new Runnable() {
2207                 @Override
2208                 public void run() {
2209                     mSessionCallback.onSetTvRecordingInfo(mSession, recordingId, recordingInfo);
2210                 }
2211             });
2212         }
2213 
postAdRequest(final AdRequest request)2214         void postAdRequest(final AdRequest request) {
2215             mHandler.post(new Runnable() {
2216                 @Override
2217                 public void run() {
2218                     if (mSession.getInputSession() != null) {
2219                         mSession.getInputSession().requestAd(request);
2220                     }
2221                 }
2222             });
2223         }
2224 
postSessionStateChanged(int state, int err)2225         void postSessionStateChanged(int state, int err) {
2226             mHandler.post(new Runnable() {
2227                 @Override
2228                 public void run() {
2229                     mSessionCallback.onSessionStateChanged(mSession, state, err);
2230                 }
2231             });
2232         }
2233 
postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId)2234         void postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) {
2235             mHandler.post(new Runnable() {
2236                 @Override
2237                 public void run() {
2238                     mSessionCallback.onBiInteractiveAppCreated(mSession, biIAppUri, biIAppId);
2239                 }
2240             });
2241         }
2242 
postTeletextAppStateChanged(int state)2243         void postTeletextAppStateChanged(int state) {
2244             mHandler.post(new Runnable() {
2245                 @Override
2246                 public void run() {
2247                     mSessionCallback.onTeletextAppStateChanged(mSession, state);
2248                 }
2249             });
2250         }
2251 
postAdBufferReady(AdBuffer buffer)2252         void postAdBufferReady(AdBuffer buffer) {
2253             mHandler.post(new Runnable() {
2254                 @Override
2255                 public void run() {
2256                     if (mSession.getInputSession() != null) {
2257                         mSession.getInputSession().notifyAdBufferReady(buffer);
2258                     }
2259                 }
2260             });
2261         }
2262     }
2263 
2264     /**
2265      * Interface used to receive the created session.
2266      * @hide
2267      */
2268     public abstract static class SessionCallback {
2269         /**
2270          * This is called after {@link TvInteractiveAppManager#createSession} has been processed.
2271          *
2272          * @param session A {@link TvInteractiveAppManager.Session} instance created. This can be
2273          *                {@code null} if the creation request failed.
2274          */
onSessionCreated(@ullable Session session)2275         public void onSessionCreated(@Nullable Session session) {
2276         }
2277 
2278         /**
2279          * This is called when {@link TvInteractiveAppManager.Session} is released.
2280          * This typically happens when the process hosting the session has crashed or been killed.
2281          *
2282          * @param session the {@link TvInteractiveAppManager.Session} instance released.
2283          */
onSessionReleased(@onNull Session session)2284         public void onSessionReleased(@NonNull Session session) {
2285         }
2286 
2287         /**
2288          * This is called when {@link TvInteractiveAppService.Session#layoutSurface} is called to
2289          * change the layout of surface.
2290          *
2291          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2292          * @param left Left position.
2293          * @param top Top position.
2294          * @param right Right position.
2295          * @param bottom Bottom position.
2296          */
onLayoutSurface(Session session, int left, int top, int right, int bottom)2297         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
2298         }
2299 
2300         /**
2301          * This is called when {@link TvInteractiveAppService.Session#requestCommand} is called.
2302          *
2303          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2304          * @param cmdType type of the command.
2305          * @param parameters parameters of the command.
2306          */
onCommandRequest( Session session, @TvInteractiveAppService.PlaybackCommandType String cmdType, Bundle parameters)2307         public void onCommandRequest(
2308                 Session session,
2309                 @TvInteractiveAppService.PlaybackCommandType String cmdType,
2310                 Bundle parameters) {
2311         }
2312 
2313         /**
2314          * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftCommand} is
2315          * called.
2316          *
2317          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2318          * @param cmdType type of the time shift command.
2319          * @param parameters parameters of the command.
2320          */
onTimeShiftCommandRequest( Session session, @TvInteractiveAppService.TimeShiftCommandType String cmdType, Bundle parameters)2321         public void onTimeShiftCommandRequest(
2322                 Session session,
2323                 @TvInteractiveAppService.TimeShiftCommandType String cmdType,
2324                 Bundle parameters) {
2325         }
2326 
2327         /**
2328          * This is called when {@link TvInteractiveAppService.Session#setVideoBounds} is called.
2329          *
2330          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2331          */
onSetVideoBounds(Session session, Rect rect)2332         public void onSetVideoBounds(Session session, Rect rect) {
2333         }
2334 
2335         /**
2336          * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds} is
2337          * called.
2338          *
2339          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2340          */
onRequestCurrentVideoBounds(Session session)2341         public void onRequestCurrentVideoBounds(Session session) {
2342         }
2343 
2344         /**
2345          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri} is
2346          * called.
2347          *
2348          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2349          */
onRequestCurrentChannelUri(Session session)2350         public void onRequestCurrentChannelUri(Session session) {
2351         }
2352 
2353         /**
2354          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelLcn} is
2355          * called.
2356          *
2357          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2358          */
onRequestCurrentChannelLcn(Session session)2359         public void onRequestCurrentChannelLcn(Session session) {
2360         }
2361 
2362         /**
2363          * This is called when {@link TvInteractiveAppService.Session#requestStreamVolume} is
2364          * called.
2365          *
2366          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2367          */
onRequestStreamVolume(Session session)2368         public void onRequestStreamVolume(Session session) {
2369         }
2370 
2371         /**
2372          * This is called when {@link TvInteractiveAppService.Session#requestTrackInfoList} is
2373          * called.
2374          *
2375          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2376          */
onRequestTrackInfoList(Session session)2377         public void onRequestTrackInfoList(Session session) {
2378         }
2379 
2380         /**
2381          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
2382          * called.
2383          *
2384          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2385          */
onRequestCurrentTvInputId(Session session)2386         public void onRequestCurrentTvInputId(Session session) {
2387         }
2388 
2389         /**
2390          * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftMode()} is
2391          * called.
2392          *
2393          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2394          */
onRequestTimeShiftMode(Session session)2395         public void onRequestTimeShiftMode(Session session) {
2396         }
2397 
2398         /**
2399          * This is called when {@link TvInteractiveAppService.Session#requestAvailableSpeeds()} is
2400          * called.
2401          *
2402          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2403          */
onRequestAvailableSpeeds(Session session)2404         public void onRequestAvailableSpeeds(Session session) {
2405         }
2406 
2407         /**
2408          * This is called when {@link TvInteractiveAppService.Session#requestStartRecording} is
2409          * called.
2410          *
2411          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2412          * @param programUri The Uri of the program to be recorded.
2413          */
onRequestStartRecording(Session session, String requestId, Uri programUri)2414         public void onRequestStartRecording(Session session, String requestId, Uri programUri) {
2415         }
2416 
2417         /**
2418          * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)}
2419          * is called.
2420          *
2421          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2422          * @param recordingId The recordingId of the recording to be stopped.
2423          */
onRequestStopRecording(Session session, String recordingId)2424         public void onRequestStopRecording(Session session, String recordingId) {
2425         }
2426 
2427         /**
2428          * This is called when
2429          * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, Uri, Bundle)}
2430          * is called.
2431          *
2432          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2433          * @param inputId The ID of the TV input for the given channel.
2434          * @param channelUri The URI of a channel to be recorded.
2435          * @param programUri The URI of the TV program to be recorded.
2436          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
2437          *            name, i.e. prefixed with a package name you own, so that different developers
2438          *            will not create conflicting keys.
2439          * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
2440          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
2441          */
onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params)2442         public void onRequestScheduleRecording(Session session, @NonNull String requestId,
2443                 @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri,
2444                 @NonNull Bundle params) {
2445         }
2446 
2447         /**
2448          * This is called when
2449          * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, long, long, int, Bundle)}
2450          * is called.
2451          *
2452          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2453          * @param inputId The ID of the TV input for the given channel.
2454          * @param channelUri The URI of a channel to be recorded.
2455          * @param startTime The start time of the recording in milliseconds since epoch.
2456          * @param duration The duration of the recording in milliseconds.
2457          * @param repeatDays The repeated days. 0 if not repeated.
2458          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
2459          *            name, i.e. prefixed with a package name you own, so that different developers
2460          *            will not create conflicting keys.
2461          * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
2462          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
2463          */
onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, int repeatDays, @NonNull Bundle params)2464         public void onRequestScheduleRecording(Session session, @NonNull String requestId,
2465                 @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration,
2466                 int repeatDays, @NonNull Bundle params) {
2467         }
2468 
2469         /**
2470          * This is called when
2471          * {@link TvInteractiveAppService.Session#setTvRecordingInfo(String, TvRecordingInfo)} is
2472          * called.
2473          *
2474          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2475          * @param recordingId The recordingId of the recording which will have the info set.
2476          * @param recordingInfo The recording info to set to the recording.
2477          */
onSetTvRecordingInfo(Session session, String recordingId, TvRecordingInfo recordingInfo)2478         public void onSetTvRecordingInfo(Session session, String recordingId,
2479                 TvRecordingInfo recordingInfo) {
2480         }
2481 
2482         /**
2483          * This is called when {@link TvInteractiveAppService.Session#requestTvRecordingInfo} is
2484          * called.
2485          *
2486          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2487          * @param recordingId The recordingId of the recording to be stopped.
2488          */
onRequestTvRecordingInfo(Session session, String recordingId)2489         public void onRequestTvRecordingInfo(Session session, String recordingId) {
2490         }
2491 
2492         /**
2493          * This is called when {@link TvInteractiveAppService.Session#requestTvRecordingInfoList} is
2494          * called.
2495          *
2496          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2497          * @param type The type of recordings to return
2498          */
onRequestTvRecordingInfoList(Session session, @TvRecordingInfo.TvRecordingListType int type)2499         public void onRequestTvRecordingInfoList(Session session,
2500                 @TvRecordingInfo.TvRecordingListType int type) {
2501         }
2502 
2503         /**
2504          * This is called when
2505          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
2506          * called.
2507          *
2508          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2509          * @param signingId the ID to identify the request.
2510          * @param algorithm the standard name of the signature algorithm requested, such as
2511          *                  MD5withRSA, SHA256withDSA, etc.
2512          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
2513          * @param data the original bytes to be signed.
2514          */
onRequestSigning( Session session, String signingId, String algorithm, String alias, byte[] data)2515         public void onRequestSigning(
2516                 Session session, String signingId, String algorithm, String alias, byte[] data) {
2517         }
2518 
2519         /**
2520          * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
2521          * called.
2522          *
2523          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2524          * @param state the current state.
2525          */
onSessionStateChanged( Session session, @InteractiveAppState int state, @ErrorCode int err)2526         public void onSessionStateChanged(
2527                 Session session,
2528                 @InteractiveAppState int state,
2529                 @ErrorCode int err) {
2530         }
2531 
2532         /**
2533          * This is called when {@link TvInteractiveAppService.Session#notifyBiInteractiveAppCreated}
2534          * is called.
2535          *
2536          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2537          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
2538          *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
2539          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
2540          *                 app.
2541          */
onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId)2542         public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) {
2543         }
2544 
2545         /**
2546          * This is called when {@link TvInteractiveAppService.Session#notifyTeletextAppStateChanged}
2547          * is called.
2548          *
2549          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2550          * @param state the current state.
2551          */
onTeletextAppStateChanged( Session session, @TvInteractiveAppManager.TeletextAppState int state)2552         public void onTeletextAppStateChanged(
2553                 Session session, @TvInteractiveAppManager.TeletextAppState int state) {
2554         }
2555     }
2556 }
2557