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.net.Uri;
37 import android.net.http.SslCertificate;
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 onRequestSelectedTrackInfo(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.postRequestSelectedTrackInfo();
517                 }
518             }
519 
520             @Override
521             public void onRequestCurrentTvInputId(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.postRequestCurrentTvInputId();
529                 }
530             }
531 
532             @Override
533             public void onRequestTimeShiftMode(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.postRequestTimeShiftMode();
541                 }
542             }
543 
544             @Override
545             public void onRequestAvailableSpeeds(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.postRequestAvailableSpeeds();
553                 }
554             }
555 
556             @Override
557             public void onRequestStartRecording(String requestId, Uri programUri, 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.postRequestStartRecording(requestId, programUri);
565                 }
566             }
567 
568             @Override
569             public void onRequestStopRecording(String recordingId, int seq) {
570                 synchronized (mSessionCallbackRecordMap) {
571                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
572                     if (record == null) {
573                         Log.e(TAG, "Callback not found for seq " + seq);
574                         return;
575                     }
576                     record.postRequestStopRecording(recordingId);
577                 }
578             }
579 
580             @Override
581             public void onRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
582                     Uri programUri, Bundle params, int seq) {
583                 synchronized (mSessionCallbackRecordMap) {
584                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
585                     if (record == null) {
586                         Log.e(TAG, "Callback not found for seq " + seq);
587                         return;
588                     }
589                     record.postRequestScheduleRecording(
590                             requestId, inputId, channelUri, programUri, params);
591                 }
592             }
593 
594             @Override
595             public void onRequestScheduleRecording2(String requestId, String inputId,
596                     Uri channelUri, long startTime, long duration, int repeatDays, Bundle params,
597                     int seq) {
598                 synchronized (mSessionCallbackRecordMap) {
599                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
600                     if (record == null) {
601                         Log.e(TAG, "Callback not found for seq " + seq);
602                         return;
603                     }
604                     record.postRequestScheduleRecording(requestId, inputId, channelUri, startTime,
605                             duration, repeatDays, params);
606                 }
607             }
608 
609             @Override
610             public void onSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo,
611                     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.postSetTvRecordingInfo(recordingId, recordingInfo);
619                 }
620             }
621 
622             @Override
623             public void onRequestTvRecordingInfo(String recordingId, 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.postRequestTvRecordingInfo(recordingId);
631                 }
632             }
633 
634             @Override
635             public void onRequestTvRecordingInfoList(int type, int seq) {
636                 synchronized (mSessionCallbackRecordMap) {
637                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
638                     if (record == null) {
639                         Log.e(TAG, "Callback not found for seq " + seq);
640                         return;
641                     }
642                     record.postRequestTvRecordingInfoList(type);
643                 }
644             }
645 
646             @Override
647             public void onRequestSigning(
648                     String id, String algorithm, String alias, byte[] data, 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.postRequestSigning(id, algorithm, alias, data);
656                 }
657             }
658 
659             @Override
660             public void onRequestSigning2(
661                     String id, String algorithm, String host, int port, byte[] data, int seq) {
662                 synchronized (mSessionCallbackRecordMap) {
663                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
664                     if (record == null) {
665                         Log.e(TAG, "Callback not found for seq " + seq);
666                         return;
667                     }
668                     record.postRequestSigning(id, algorithm, host, port, data);
669                 }
670             }
671 
672             @Override
673             public void onRequestCertificate(String host, int port, int seq) {
674                 synchronized (mSessionCallbackRecordMap) {
675                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
676                     if (record == null) {
677                         Log.e(TAG, "Callback not found for seq " + seq);
678                         return;
679                     }
680                     record.postRequestCertificate(host, port);
681                 }
682             }
683 
684             @Override
685             public void onSessionStateChanged(int state, int err, int seq) {
686                 synchronized (mSessionCallbackRecordMap) {
687                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
688                     if (record == null) {
689                         Log.e(TAG, "Callback not found for seq " + seq);
690                         return;
691                     }
692                     record.postSessionStateChanged(state, err);
693                 }
694             }
695 
696             @Override
697             public void onBiInteractiveAppCreated(Uri biIAppUri, String biIAppId, int seq) {
698                 synchronized (mSessionCallbackRecordMap) {
699                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
700                     if (record == null) {
701                         Log.e(TAG, "Callback not found for seq " + seq);
702                         return;
703                     }
704                     record.postBiInteractiveAppCreated(biIAppUri, biIAppId);
705                 }
706             }
707 
708             @Override
709             public void onTeletextAppStateChanged(int state, int seq) {
710                 synchronized (mSessionCallbackRecordMap) {
711                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
712                     if (record == null) {
713                         Log.e(TAG, "Callback not found for seq " + seq);
714                         return;
715                     }
716                     record.postTeletextAppStateChanged(state);
717                 }
718             }
719 
720             @Override
721             public void onAdBufferReady(AdBuffer buffer, int seq) {
722                 synchronized (mSessionCallbackRecordMap) {
723                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
724                     if (record == null) {
725                         Log.e(TAG, "Callback not found for seq " + seq);
726                         return;
727                     }
728                     record.postAdBufferReady(buffer);
729                 }
730             }
731         };
732         ITvInteractiveAppManagerCallback managerCallback =
733                 new ITvInteractiveAppManagerCallback.Stub() {
734             @Override
735             public void onInteractiveAppServiceAdded(String iAppServiceId) {
736                 synchronized (mLock) {
737                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
738                         record.postInteractiveAppServiceAdded(iAppServiceId);
739                     }
740                 }
741             }
742 
743             @Override
744             public void onInteractiveAppServiceRemoved(String iAppServiceId) {
745                 synchronized (mLock) {
746                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
747                         record.postInteractiveAppServiceRemoved(iAppServiceId);
748                     }
749                 }
750             }
751 
752             @Override
753             public void onInteractiveAppServiceUpdated(String iAppServiceId) {
754                 synchronized (mLock) {
755                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
756                         record.postInteractiveAppServiceUpdated(iAppServiceId);
757                     }
758                 }
759             }
760 
761             @Override
762             public void onTvInteractiveAppServiceInfoUpdated(TvInteractiveAppServiceInfo iAppInfo) {
763                 // TODO: add public API updateInteractiveAppInfo()
764                 synchronized (mLock) {
765                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
766                         record.postTvInteractiveAppServiceInfoUpdated(iAppInfo);
767                     }
768                 }
769             }
770 
771             @Override
772             public void onStateChanged(String iAppServiceId, int type, int state, int err) {
773                 synchronized (mLock) {
774                     for (TvInteractiveAppCallbackRecord record : mCallbackRecords) {
775                         record.postStateChanged(iAppServiceId, type, state, err);
776                     }
777                 }
778             }
779         };
780         try {
781             if (mService != null) {
782                 mService.registerCallback(managerCallback, mUserId);
783             }
784         } catch (RemoteException e) {
785             throw e.rethrowFromSystemServer();
786         }
787     }
788 
789     /**
790      * Callback used to monitor status of the TV Interactive App.
791      */
792     public abstract static class TvInteractiveAppCallback {
793         /**
794          * This is called when a TV Interactive App service is added to the system.
795          *
796          * <p>Normally it happens when the user installs a new TV Interactive App service package
797          * that implements {@link TvInteractiveAppService} interface.
798          *
799          * @param iAppServiceId The ID of the TV Interactive App service.
800          */
onInteractiveAppServiceAdded(@onNull String iAppServiceId)801         public void onInteractiveAppServiceAdded(@NonNull String iAppServiceId) {
802         }
803 
804         /**
805          * This is called when a TV Interactive App service is removed from the system.
806          *
807          * <p>Normally it happens when the user uninstalls the previously installed TV Interactive
808          * App service package.
809          *
810          * @param iAppServiceId The ID of the TV Interactive App service.
811          */
onInteractiveAppServiceRemoved(@onNull String iAppServiceId)812         public void onInteractiveAppServiceRemoved(@NonNull String iAppServiceId) {
813         }
814 
815         /**
816          * This is called when a TV Interactive App service is updated on the system.
817          *
818          * <p>Normally it happens when a previously installed TV Interactive App service package is
819          * re-installed or a newer version of the package exists becomes available/unavailable.
820          *
821          * @param iAppServiceId The ID of the TV Interactive App service.
822          */
onInteractiveAppServiceUpdated(@onNull String iAppServiceId)823         public void onInteractiveAppServiceUpdated(@NonNull String iAppServiceId) {
824         }
825 
826         /**
827          * This is called when the information about an existing TV Interactive App service has been
828          * updated.
829          *
830          * <p>Because the system automatically creates a <code>TvInteractiveAppServiceInfo</code>
831          * object for each TV Interactive App service based on the information collected from the
832          * <code>AndroidManifest.xml</code>, this method is only called back when such information
833          * has changed dynamically.
834          *
835          * @param iAppInfo The <code>TvInteractiveAppServiceInfo</code> object that contains new
836          *                 information.
837          * @hide
838          */
onTvInteractiveAppServiceInfoUpdated( @onNull TvInteractiveAppServiceInfo iAppInfo)839         public void onTvInteractiveAppServiceInfoUpdated(
840                 @NonNull TvInteractiveAppServiceInfo iAppInfo) {
841         }
842 
843         /**
844          * This is called when the state of the interactive app service is changed.
845          *
846          * @param iAppServiceId The ID of the TV Interactive App service.
847          * @param type the interactive app type
848          * @param state the current state of the service of the given type
849          * @param err the error code for error state. {@link #ERROR_NONE} is used when the state is
850          *            not {@link #SERVICE_STATE_ERROR}.
851          */
onTvInteractiveAppServiceStateChanged( @onNull String iAppServiceId, @TvInteractiveAppServiceInfo.InteractiveAppType int type, @ServiceState int state, @ErrorCode int err)852         public void onTvInteractiveAppServiceStateChanged(
853                 @NonNull String iAppServiceId,
854                 @TvInteractiveAppServiceInfo.InteractiveAppType int type,
855                 @ServiceState int state,
856                 @ErrorCode int err) {
857         }
858     }
859 
860     private static final class TvInteractiveAppCallbackRecord {
861         private final TvInteractiveAppCallback mCallback;
862         private final Executor mExecutor;
863 
TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor)864         TvInteractiveAppCallbackRecord(TvInteractiveAppCallback callback, Executor executor) {
865             mCallback = callback;
866             mExecutor = executor;
867         }
868 
getCallback()869         public TvInteractiveAppCallback getCallback() {
870             return mCallback;
871         }
872 
postInteractiveAppServiceAdded(final String iAppServiceId)873         public void postInteractiveAppServiceAdded(final String iAppServiceId) {
874             mExecutor.execute(new Runnable() {
875                 @Override
876                 public void run() {
877                     mCallback.onInteractiveAppServiceAdded(iAppServiceId);
878                 }
879             });
880         }
881 
postInteractiveAppServiceRemoved(final String iAppServiceId)882         public void postInteractiveAppServiceRemoved(final String iAppServiceId) {
883             mExecutor.execute(new Runnable() {
884                 @Override
885                 public void run() {
886                     mCallback.onInteractiveAppServiceRemoved(iAppServiceId);
887                 }
888             });
889         }
890 
postInteractiveAppServiceUpdated(final String iAppServiceId)891         public void postInteractiveAppServiceUpdated(final String iAppServiceId) {
892             mExecutor.execute(new Runnable() {
893                 @Override
894                 public void run() {
895                     mCallback.onInteractiveAppServiceUpdated(iAppServiceId);
896                 }
897             });
898         }
899 
postTvInteractiveAppServiceInfoUpdated( final TvInteractiveAppServiceInfo iAppInfo)900         public void postTvInteractiveAppServiceInfoUpdated(
901                 final TvInteractiveAppServiceInfo iAppInfo) {
902             mExecutor.execute(new Runnable() {
903                 @Override
904                 public void run() {
905                     mCallback.onTvInteractiveAppServiceInfoUpdated(iAppInfo);
906                 }
907             });
908         }
909 
postStateChanged(String iAppServiceId, int type, int state, int err)910         public void postStateChanged(String iAppServiceId, int type, int state, int err) {
911             mExecutor.execute(new Runnable() {
912                 @Override
913                 public void run() {
914                     mCallback.onTvInteractiveAppServiceStateChanged(
915                             iAppServiceId, type, state, err);
916                 }
917             });
918         }
919     }
920 
921     /**
922      * Creates a {@link Session} for a given TV interactive application.
923      *
924      * <p>The number of sessions that can be created at the same time is limited by the capability
925      * of the given interactive application.
926      *
927      * @param iAppServiceId The ID of the interactive application.
928      * @param type the type of the interactive application.
929      * @param callback A callback used to receive the created session.
930      * @param handler A {@link Handler} that the session creation will be delivered to.
931      * @hide
932      */
createSession(@onNull String iAppServiceId, int type, @NonNull final SessionCallback callback, @NonNull Handler handler)933     public void createSession(@NonNull String iAppServiceId, int type,
934             @NonNull final SessionCallback callback, @NonNull Handler handler) {
935         createSessionInternal(iAppServiceId, type, callback, handler);
936     }
937 
createSessionInternal(String iAppServiceId, int type, SessionCallback callback, Handler handler)938     private void createSessionInternal(String iAppServiceId, int type, SessionCallback callback,
939             Handler handler) {
940         Preconditions.checkNotNull(iAppServiceId);
941         Preconditions.checkNotNull(callback);
942         Preconditions.checkNotNull(handler);
943         SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
944         synchronized (mSessionCallbackRecordMap) {
945             int seq = mNextSeq++;
946             mSessionCallbackRecordMap.put(seq, record);
947             try {
948                 mService.createSession(mClient, iAppServiceId, type, seq, mUserId);
949             } catch (RemoteException e) {
950                 throw e.rethrowFromSystemServer();
951             }
952         }
953     }
954 
955     /**
956      * Returns the complete list of TV Interactive App service on the system.
957      *
958      * @return List of {@link TvInteractiveAppServiceInfo} for each TV Interactive App service that
959      *         describes its meta information.
960      */
961     @NonNull
getTvInteractiveAppServiceList()962     public List<TvInteractiveAppServiceInfo> getTvInteractiveAppServiceList() {
963         try {
964             return mService.getTvInteractiveAppServiceList(mUserId);
965         } catch (RemoteException e) {
966             throw e.rethrowFromSystemServer();
967         }
968     }
969 
970     /**
971      * Returns a list of available app link information.
972      *
973      * <P>A package must declare its app link info in its manifest using meta-data tag, so the info
974      * can be detected by the system.
975      *
976      * @return List of {@link AppLinkInfo} for each package that deslares its app link information.
977      */
978     @NonNull
getAppLinkInfoList()979     public List<AppLinkInfo> getAppLinkInfoList() {
980         try {
981             return mService.getAppLinkInfoList(mUserId);
982         } catch (RemoteException e) {
983             throw e.rethrowFromSystemServer();
984         }
985     }
986 
987     /**
988      * Registers an Android application link info record which can be used to launch the specific
989      * Android application by TV interactive App RTE.
990      *
991      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
992      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
993      * @param appLinkInfo The Android application link info record to be registered.
994      */
registerAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)995     public void registerAppLinkInfo(
996             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
997         try {
998             mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
999         } catch (RemoteException e) {
1000             throw e.rethrowFromSystemServer();
1001         }
1002     }
1003 
1004     /**
1005      * Unregisters an Android application link info record which can be used to launch the specific
1006      * Android application by TV interactive App RTE.
1007      *
1008      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
1009      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
1010      * @param appLinkInfo The Android application link info record to be unregistered.
1011      */
unregisterAppLinkInfo( @onNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo)1012     public void unregisterAppLinkInfo(
1013             @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) {
1014         try {
1015             mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
1016         } catch (RemoteException e) {
1017             throw e.rethrowFromSystemServer();
1018         }
1019     }
1020 
1021     /**
1022      * Sends app link command.
1023      *
1024      * @param tvIAppServiceId The ID of TV interactive service which the command to be sent to. The
1025      *                        ID can be found in {@link TvInteractiveAppServiceInfo#getId()}.
1026      * @param command The command to be sent.
1027      */
sendAppLinkCommand(@onNull String tvIAppServiceId, @NonNull Bundle command)1028     public void sendAppLinkCommand(@NonNull String tvIAppServiceId, @NonNull Bundle command) {
1029         try {
1030             mService.sendAppLinkCommand(tvIAppServiceId, command, mUserId);
1031         } catch (RemoteException e) {
1032             throw e.rethrowFromSystemServer();
1033         }
1034     }
1035 
1036     /**
1037      * Registers a {@link TvInteractiveAppCallback}.
1038      *
1039      * @param callback A callback used to monitor status of the TV Interactive App services.
1040      * @param executor A {@link Executor} that the status change will be delivered to.
1041      */
registerCallback( @allbackExecutor @onNull Executor executor, @NonNull TvInteractiveAppCallback callback)1042     public void registerCallback(
1043             @CallbackExecutor @NonNull Executor executor,
1044             @NonNull TvInteractiveAppCallback callback) {
1045         Preconditions.checkNotNull(callback);
1046         Preconditions.checkNotNull(executor);
1047         synchronized (mLock) {
1048             mCallbackRecords.add(new TvInteractiveAppCallbackRecord(callback, executor));
1049         }
1050     }
1051 
1052     /**
1053      * Unregisters the existing {@link TvInteractiveAppCallback}.
1054      *
1055      * @param callback The existing callback to remove.
1056      */
unregisterCallback(@onNull final TvInteractiveAppCallback callback)1057     public void unregisterCallback(@NonNull final TvInteractiveAppCallback callback) {
1058         Preconditions.checkNotNull(callback);
1059         synchronized (mLock) {
1060             for (Iterator<TvInteractiveAppCallbackRecord> it = mCallbackRecords.iterator();
1061                     it.hasNext(); ) {
1062                 TvInteractiveAppCallbackRecord record = it.next();
1063                 if (record.getCallback() == callback) {
1064                     it.remove();
1065                     break;
1066                 }
1067             }
1068         }
1069     }
1070 
1071     /**
1072      * The Session provides the per-session functionality of interactive app.
1073      * @hide
1074      */
1075     public static final class Session {
1076         static final int DISPATCH_IN_PROGRESS = -1;
1077         static final int DISPATCH_NOT_HANDLED = 0;
1078         static final int DISPATCH_HANDLED = 1;
1079 
1080         private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
1081 
1082         private final ITvInteractiveAppManager mService;
1083         private final int mUserId;
1084         private final int mSeq;
1085         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
1086 
1087         // For scheduling input event handling on the main thread. This also serves as a lock to
1088         // protect pending input events and the input channel.
1089         private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
1090 
1091         private TvInputManager.Session mInputSession;
1092         private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
1093         private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
1094 
1095         private IBinder mToken;
1096         private TvInputEventSender mSender;
1097         private InputChannel mInputChannel;
1098 
Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service, int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap)1099         private Session(IBinder token, InputChannel channel, ITvInteractiveAppManager service,
1100                 int userId, int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
1101             mToken = token;
1102             mInputChannel = channel;
1103             mService = service;
1104             mUserId = userId;
1105             mSeq = seq;
1106             mSessionCallbackRecordMap = sessionCallbackRecordMap;
1107         }
1108 
getInputSession()1109         public TvInputManager.Session getInputSession() {
1110             return mInputSession;
1111         }
1112 
setInputSession(TvInputManager.Session inputSession)1113         public void setInputSession(TvInputManager.Session inputSession) {
1114             mInputSession = inputSession;
1115         }
1116 
startInteractiveApp()1117         void startInteractiveApp() {
1118             if (mToken == null) {
1119                 Log.w(TAG, "The session has been already released");
1120                 return;
1121             }
1122             try {
1123                 mService.startInteractiveApp(mToken, mUserId);
1124             } catch (RemoteException e) {
1125                 throw e.rethrowFromSystemServer();
1126             }
1127         }
1128 
stopInteractiveApp()1129         void stopInteractiveApp() {
1130             if (mToken == null) {
1131                 Log.w(TAG, "The session has been already released");
1132                 return;
1133             }
1134             try {
1135                 mService.stopInteractiveApp(mToken, mUserId);
1136             } catch (RemoteException e) {
1137                 throw e.rethrowFromSystemServer();
1138             }
1139         }
1140 
resetInteractiveApp()1141         void resetInteractiveApp() {
1142             if (mToken == null) {
1143                 Log.w(TAG, "The session has been already released");
1144                 return;
1145             }
1146             try {
1147                 mService.resetInteractiveApp(mToken, mUserId);
1148             } catch (RemoteException e) {
1149                 throw e.rethrowFromSystemServer();
1150             }
1151         }
1152 
createBiInteractiveApp(Uri biIAppUri, Bundle params)1153         void createBiInteractiveApp(Uri biIAppUri, Bundle params) {
1154             if (mToken == null) {
1155                 Log.w(TAG, "The session has been already released");
1156                 return;
1157             }
1158             try {
1159                 mService.createBiInteractiveApp(mToken, biIAppUri, params, mUserId);
1160             } catch (RemoteException e) {
1161                 throw e.rethrowFromSystemServer();
1162             }
1163         }
1164 
destroyBiInteractiveApp(String biIAppId)1165         void destroyBiInteractiveApp(String biIAppId) {
1166             if (mToken == null) {
1167                 Log.w(TAG, "The session has been already released");
1168                 return;
1169             }
1170             try {
1171                 mService.destroyBiInteractiveApp(mToken, biIAppId, mUserId);
1172             } catch (RemoteException e) {
1173                 throw e.rethrowFromSystemServer();
1174             }
1175         }
1176 
setTeletextAppEnabled(boolean enable)1177         void setTeletextAppEnabled(boolean enable) {
1178             if (mToken == null) {
1179                 Log.w(TAG, "The session has been already released");
1180                 return;
1181             }
1182             try {
1183                 mService.setTeletextAppEnabled(mToken, enable, mUserId);
1184             } catch (RemoteException e) {
1185                 throw e.rethrowFromSystemServer();
1186             }
1187         }
1188 
sendCurrentVideoBounds(@onNull Rect bounds)1189         void sendCurrentVideoBounds(@NonNull Rect bounds) {
1190             if (mToken == null) {
1191                 Log.w(TAG, "The session has been already released");
1192                 return;
1193             }
1194             try {
1195                 mService.sendCurrentVideoBounds(mToken, bounds, mUserId);
1196             } catch (RemoteException e) {
1197                 throw e.rethrowFromSystemServer();
1198             }
1199         }
1200 
sendCurrentChannelUri(@ullable Uri channelUri)1201         void sendCurrentChannelUri(@Nullable Uri channelUri) {
1202             if (mToken == null) {
1203                 Log.w(TAG, "The session has been already released");
1204                 return;
1205             }
1206             try {
1207                 mService.sendCurrentChannelUri(mToken, channelUri, mUserId);
1208             } catch (RemoteException e) {
1209                 throw e.rethrowFromSystemServer();
1210             }
1211         }
1212 
sendCurrentChannelLcn(int lcn)1213         void sendCurrentChannelLcn(int lcn) {
1214             if (mToken == null) {
1215                 Log.w(TAG, "The session has been already released");
1216                 return;
1217             }
1218             try {
1219                 mService.sendCurrentChannelLcn(mToken, lcn, mUserId);
1220             } catch (RemoteException e) {
1221                 throw e.rethrowFromSystemServer();
1222             }
1223         }
1224 
sendStreamVolume(float volume)1225         void sendStreamVolume(float volume) {
1226             if (mToken == null) {
1227                 Log.w(TAG, "The session has been already released");
1228                 return;
1229             }
1230             try {
1231                 mService.sendStreamVolume(mToken, volume, mUserId);
1232             } catch (RemoteException e) {
1233                 throw e.rethrowFromSystemServer();
1234             }
1235         }
1236 
sendTrackInfoList(@onNull List<TvTrackInfo> tracks)1237         void sendTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
1238             if (mToken == null) {
1239                 Log.w(TAG, "The session has been already released");
1240                 return;
1241             }
1242             try {
1243                 mService.sendTrackInfoList(mToken, tracks, mUserId);
1244             } catch (RemoteException e) {
1245                 throw e.rethrowFromSystemServer();
1246             }
1247         }
1248 
sendSelectedTrackInfo(@onNull List<TvTrackInfo> tracks)1249         void sendSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
1250             if (mToken == null) {
1251                 Log.w(TAG, "The session has been already released");
1252                 return;
1253             }
1254             try {
1255                 mService.sendSelectedTrackInfo(mToken, tracks, mUserId);
1256             } catch (RemoteException e) {
1257                 throw e.rethrowFromSystemServer();
1258             }
1259         }
1260 
sendCurrentTvInputId(@ullable String inputId)1261         void sendCurrentTvInputId(@Nullable String inputId) {
1262             if (mToken == null) {
1263                 Log.w(TAG, "The session has been already released");
1264                 return;
1265             }
1266             try {
1267                 mService.sendCurrentTvInputId(mToken, inputId, mUserId);
1268             } catch (RemoteException e) {
1269                 throw e.rethrowFromSystemServer();
1270             }
1271         }
1272 
sendTimeShiftMode(int mode)1273         void sendTimeShiftMode(int mode) {
1274             if (mToken == null) {
1275                 Log.w(TAG, "The session has been already released");
1276                 return;
1277             }
1278             try {
1279                 mService.sendTimeShiftMode(mToken, mode, mUserId);
1280             } catch (RemoteException e) {
1281                 throw e.rethrowFromSystemServer();
1282             }
1283         }
1284 
sendAvailableSpeeds(float[] speeds)1285         void sendAvailableSpeeds(float[] speeds) {
1286             if (mToken == null) {
1287                 Log.w(TAG, "The session has been already released");
1288                 return;
1289             }
1290             try {
1291                 mService.sendAvailableSpeeds(mToken, speeds, mUserId);
1292             } catch (RemoteException e) {
1293                 throw e.rethrowFromSystemServer();
1294             }
1295         }
1296 
sendTvRecordingInfo(@ullable TvRecordingInfo recordingInfo)1297         void sendTvRecordingInfo(@Nullable TvRecordingInfo recordingInfo) {
1298             if (mToken == null) {
1299                 Log.w(TAG, "The session has been already released");
1300                 return;
1301             }
1302             try {
1303                 mService.sendTvRecordingInfo(mToken, recordingInfo, mUserId);
1304             } catch (RemoteException e) {
1305                 throw e.rethrowFromSystemServer();
1306             }
1307         }
1308 
sendTvRecordingInfoList(@ullable List<TvRecordingInfo> recordingInfoList)1309         void sendTvRecordingInfoList(@Nullable List<TvRecordingInfo> recordingInfoList) {
1310             if (mToken == null) {
1311                 Log.w(TAG, "The session has been already released");
1312                 return;
1313             }
1314             try {
1315                 mService.sendTvRecordingInfoList(mToken, recordingInfoList, mUserId);
1316             } catch (RemoteException e) {
1317                 throw e.rethrowFromSystemServer();
1318             }
1319         }
1320 
notifyRecordingStarted(String recordingId, String requestId)1321         void notifyRecordingStarted(String recordingId, String requestId) {
1322             if (mToken == null) {
1323                 Log.w(TAG, "The session has been already released");
1324                 return;
1325             }
1326             try {
1327                 mService.notifyRecordingStarted(mToken, recordingId, requestId, mUserId);
1328             } catch (RemoteException e) {
1329                 throw e.rethrowFromSystemServer();
1330             }
1331         }
1332 
notifyRecordingStopped(String recordingId)1333         void notifyRecordingStopped(String recordingId) {
1334             if (mToken == null) {
1335                 Log.w(TAG, "The session has been already released");
1336                 return;
1337             }
1338             try {
1339                 mService.notifyRecordingStopped(mToken, recordingId, mUserId);
1340             } catch (RemoteException e) {
1341                 throw e.rethrowFromSystemServer();
1342             }
1343         }
1344 
sendSigningResult(@onNull String signingId, @NonNull byte[] result)1345         void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
1346             if (mToken == null) {
1347                 Log.w(TAG, "The session has been already released");
1348                 return;
1349             }
1350             try {
1351                 mService.sendSigningResult(mToken, signingId, result, mUserId);
1352             } catch (RemoteException e) {
1353                 throw e.rethrowFromSystemServer();
1354             }
1355         }
1356 
sendCertificate(@onNull String host, int port, @NonNull SslCertificate cert)1357         void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
1358             if (mToken == null) {
1359                 Log.w(TAG, "The session has been already released");
1360                 return;
1361             }
1362             try {
1363                 mService.sendCertificate(mToken, host, port, SslCertificate.saveState(cert),
1364                         mUserId);
1365             } catch (RemoteException e) {
1366                 throw e.rethrowFromSystemServer();
1367             }
1368         }
1369 
notifyError(@onNull String errMsg, @NonNull Bundle params)1370         void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
1371             if (mToken == null) {
1372                 Log.w(TAG, "The session has been already released");
1373                 return;
1374             }
1375             try {
1376                 mService.notifyError(mToken, errMsg, params, mUserId);
1377             } catch (RemoteException e) {
1378                 throw e.rethrowFromSystemServer();
1379             }
1380         }
1381 
notifyTimeShiftPlaybackParams(@onNull PlaybackParams params)1382         void notifyTimeShiftPlaybackParams(@NonNull PlaybackParams params) {
1383             if (mToken == null) {
1384                 Log.w(TAG, "The session has been already released");
1385                 return;
1386             }
1387             try {
1388                 mService.notifyTimeShiftPlaybackParams(mToken, params, mUserId);
1389             } catch (RemoteException e) {
1390                 throw e.rethrowFromSystemServer();
1391             }
1392         }
1393 
notifyTimeShiftStatusChanged( @onNull String inputId, @TvInputManager.TimeShiftStatus int status)1394         void notifyTimeShiftStatusChanged(
1395                 @NonNull String inputId, @TvInputManager.TimeShiftStatus int status) {
1396             if (mToken == null) {
1397                 Log.w(TAG, "The session has been already released");
1398                 return;
1399             }
1400             try {
1401                 mService.notifyTimeShiftStatusChanged(mToken, inputId, status, mUserId);
1402             } catch (RemoteException e) {
1403                 throw e.rethrowFromSystemServer();
1404             }
1405         }
1406 
notifyTimeShiftStartPositionChanged(@onNull String inputId, long timeMs)1407         void notifyTimeShiftStartPositionChanged(@NonNull String inputId, long timeMs) {
1408             if (mToken == null) {
1409                 Log.w(TAG, "The session has been already released");
1410                 return;
1411             }
1412             try {
1413                 mService.notifyTimeShiftStartPositionChanged(mToken, inputId, timeMs, mUserId);
1414             } catch (RemoteException e) {
1415                 throw e.rethrowFromSystemServer();
1416             }
1417         }
1418 
notifyTimeShiftCurrentPositionChanged(@onNull String inputId, long timeMs)1419         void notifyTimeShiftCurrentPositionChanged(@NonNull String inputId, long timeMs) {
1420             if (mToken == null) {
1421                 Log.w(TAG, "The session has been already released");
1422                 return;
1423             }
1424             try {
1425                 mService.notifyTimeShiftCurrentPositionChanged(mToken, inputId, timeMs, mUserId);
1426             } catch (RemoteException e) {
1427                 throw e.rethrowFromSystemServer();
1428             }
1429         }
1430 
notifyRecordingConnectionFailed(@onNull String recordingId, @NonNull String inputId)1431         void notifyRecordingConnectionFailed(@NonNull String recordingId, @NonNull String inputId) {
1432             if (mToken == null) {
1433                 Log.w(TAG, "The session has been already released");
1434                 return;
1435             }
1436             try {
1437                 mService.notifyRecordingConnectionFailed(mToken, recordingId, inputId, mUserId);
1438             } catch (RemoteException e) {
1439                 throw e.rethrowFromSystemServer();
1440             }
1441         }
1442 
notifyRecordingDisconnected(@onNull String recordingId, @NonNull String inputId)1443         void notifyRecordingDisconnected(@NonNull String recordingId, @NonNull String inputId) {
1444             if (mToken == null) {
1445                 Log.w(TAG, "The session has been already released");
1446                 return;
1447             }
1448             try {
1449                 mService.notifyRecordingDisconnected(mToken, recordingId, inputId, mUserId);
1450             } catch (RemoteException e) {
1451                 throw e.rethrowFromSystemServer();
1452             }
1453         }
1454 
notifyRecordingTuned(@onNull String recordingId, @NonNull Uri channelUri)1455         void notifyRecordingTuned(@NonNull String recordingId, @NonNull Uri channelUri) {
1456             if (mToken == null) {
1457                 Log.w(TAG, "The session has been already released");
1458                 return;
1459             }
1460             try {
1461                 mService.notifyRecordingTuned(mToken, recordingId, channelUri, mUserId);
1462             } catch (RemoteException e) {
1463                 throw e.rethrowFromSystemServer();
1464             }
1465         }
1466 
notifyRecordingError(@onNull String recordingId, int err)1467         void notifyRecordingError(@NonNull String recordingId, int err) {
1468             if (mToken == null) {
1469                 Log.w(TAG, "The session has been already released");
1470                 return;
1471             }
1472             try {
1473                 mService.notifyRecordingError(mToken, recordingId, err, mUserId);
1474             } catch (RemoteException e) {
1475                 throw e.rethrowFromSystemServer();
1476             }
1477         }
1478 
notifyRecordingScheduled(@onNull String recordingId, @Nullable String requestId)1479         void notifyRecordingScheduled(@NonNull String recordingId, @Nullable String requestId) {
1480             if (mToken == null) {
1481                 Log.w(TAG, "The session has been already released");
1482                 return;
1483             }
1484             try {
1485                 mService.notifyRecordingScheduled(mToken, recordingId, requestId, mUserId);
1486             } catch (RemoteException e) {
1487                 throw e.rethrowFromSystemServer();
1488             }
1489         }
1490 
1491         /**
1492          * Sets the {@link android.view.Surface} for this session.
1493          *
1494          * @param surface A {@link android.view.Surface} used to render video.
1495          */
setSurface(Surface surface)1496         public void setSurface(Surface surface) {
1497             if (mToken == null) {
1498                 Log.w(TAG, "The session has been already released");
1499                 return;
1500             }
1501             // surface can be null.
1502             try {
1503                 mService.setSurface(mToken, surface, mUserId);
1504             } catch (RemoteException e) {
1505                 throw e.rethrowFromSystemServer();
1506             }
1507         }
1508 
1509         /**
1510          * Creates a media view. Once the media view is created, {@link #relayoutMediaView}
1511          * should be called whenever the layout of its containing view is changed.
1512          * {@link #removeMediaView()} should be called to remove the media view.
1513          * Since a session can have only one media view, this method should be called only once
1514          * or it can be called again after calling {@link #removeMediaView()}.
1515          *
1516          * @param view A view for interactive app.
1517          * @param frame A position of the media view.
1518          * @throws IllegalStateException if {@code view} is not attached to a window.
1519          */
createMediaView(@onNull View view, @NonNull Rect frame)1520         void createMediaView(@NonNull View view, @NonNull Rect frame) {
1521             Preconditions.checkNotNull(view);
1522             Preconditions.checkNotNull(frame);
1523             if (view.getWindowToken() == null) {
1524                 throw new IllegalStateException("view must be attached to a window");
1525             }
1526             if (mToken == null) {
1527                 Log.w(TAG, "The session has been already released");
1528                 return;
1529             }
1530             try {
1531                 mService.createMediaView(mToken, view.getWindowToken(), frame, mUserId);
1532             } catch (RemoteException e) {
1533                 throw e.rethrowFromSystemServer();
1534             }
1535         }
1536 
1537         /**
1538          * Relayouts the current media view.
1539          *
1540          * @param frame A new position of the media view.
1541          */
relayoutMediaView(@onNull Rect frame)1542         void relayoutMediaView(@NonNull Rect frame) {
1543             Preconditions.checkNotNull(frame);
1544             if (mToken == null) {
1545                 Log.w(TAG, "The session has been already released");
1546                 return;
1547             }
1548             try {
1549                 mService.relayoutMediaView(mToken, frame, mUserId);
1550             } catch (RemoteException e) {
1551                 throw e.rethrowFromSystemServer();
1552             }
1553         }
1554 
1555         /**
1556          * Removes the current media view.
1557          */
removeMediaView()1558         void removeMediaView() {
1559             if (mToken == null) {
1560                 Log.w(TAG, "The session has been already released");
1561                 return;
1562             }
1563             try {
1564                 mService.removeMediaView(mToken, mUserId);
1565             } catch (RemoteException e) {
1566                 throw e.rethrowFromSystemServer();
1567             }
1568         }
1569 
1570         /**
1571          * Notifies of any structural changes (format or size) of the surface passed in
1572          * {@link #setSurface}.
1573          *
1574          * @param format The new PixelFormat of the surface.
1575          * @param width The new width of the surface.
1576          * @param height The new height of the surface.
1577          */
dispatchSurfaceChanged(int format, int width, int height)1578         public void dispatchSurfaceChanged(int format, int width, int height) {
1579             if (mToken == null) {
1580                 Log.w(TAG, "The session has been already released");
1581                 return;
1582             }
1583             try {
1584                 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
1585             } catch (RemoteException e) {
1586                 throw e.rethrowFromSystemServer();
1587             }
1588         }
1589 
1590         /**
1591          * Dispatches an input event to this session.
1592          *
1593          * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
1594          * @param token A token used to identify the input event later in the callback.
1595          * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
1596          * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
1597          *            {@code null}.
1598          * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
1599          *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
1600          *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
1601          *         be invoked later.
1602          * @hide
1603          */
dispatchInputEvent(@onNull InputEvent event, Object token, @NonNull FinishedInputEventCallback callback, @NonNull Handler handler)1604         public int dispatchInputEvent(@NonNull InputEvent event, Object token,
1605                 @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
1606             Preconditions.checkNotNull(event);
1607             Preconditions.checkNotNull(callback);
1608             Preconditions.checkNotNull(handler);
1609             synchronized (mHandler) {
1610                 if (mInputChannel == null) {
1611                     return DISPATCH_NOT_HANDLED;
1612                 }
1613                 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
1614                 if (Looper.myLooper() == Looper.getMainLooper()) {
1615                     // Already running on the main thread so we can send the event immediately.
1616                     return sendInputEventOnMainLooperLocked(p);
1617                 }
1618 
1619                 // Post the event to the main thread.
1620                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
1621                 msg.setAsynchronous(true);
1622                 mHandler.sendMessage(msg);
1623                 return DISPATCH_IN_PROGRESS;
1624             }
1625         }
1626 
1627         /**
1628          * Notifies of any broadcast info response passed in from TIS.
1629          *
1630          * @param response response passed in from TIS.
1631          */
notifyBroadcastInfoResponse(BroadcastInfoResponse response)1632         public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) {
1633             if (mToken == null) {
1634                 Log.w(TAG, "The session has been already released");
1635                 return;
1636             }
1637             try {
1638                 mService.notifyBroadcastInfoResponse(mToken, response, mUserId);
1639             } catch (RemoteException e) {
1640                 throw e.rethrowFromSystemServer();
1641             }
1642         }
1643 
1644         /**
1645          * Notifies of any advertisement response passed in from TIS.
1646          *
1647          * @param response response passed in from TIS.
1648          */
notifyAdResponse(AdResponse response)1649         public void notifyAdResponse(AdResponse response) {
1650             if (mToken == null) {
1651                 Log.w(TAG, "The session has been already released");
1652                 return;
1653             }
1654             try {
1655                 mService.notifyAdResponse(mToken, response, mUserId);
1656             } catch (RemoteException e) {
1657                 throw e.rethrowFromSystemServer();
1658             }
1659         }
1660 
1661         /**
1662          * Notifies the advertisement buffer is consumed.
1663          */
notifyAdBufferConsumed(AdBuffer buffer)1664         public void notifyAdBufferConsumed(AdBuffer buffer) {
1665             if (mToken == null) {
1666                 Log.w(TAG, "The session has been already released");
1667                 return;
1668             }
1669             try {
1670                 mService.notifyAdBufferConsumed(mToken, buffer, mUserId);
1671             } catch (RemoteException e) {
1672                 throw e.rethrowFromSystemServer();
1673             } finally {
1674                 if (buffer != null) {
1675                     buffer.getSharedMemory().close();
1676                 }
1677             }
1678         }
1679 
1680         /**
1681          * Releases this session.
1682          */
release()1683         public void release() {
1684             if (mToken == null) {
1685                 Log.w(TAG, "The session has been already released");
1686                 return;
1687             }
1688             try {
1689                 mService.releaseSession(mToken, mUserId);
1690             } catch (RemoteException e) {
1691                 throw e.rethrowFromSystemServer();
1692             }
1693 
1694             releaseInternal();
1695         }
1696 
1697         /**
1698          * Notifies Interactive APP session when a channel is tuned.
1699          */
notifyTuned(Uri channelUri)1700         public void notifyTuned(Uri channelUri) {
1701             if (mToken == null) {
1702                 Log.w(TAG, "The session has been already released");
1703                 return;
1704             }
1705             try {
1706                 mService.notifyTuned(mToken, channelUri, mUserId);
1707             } catch (RemoteException e) {
1708                 throw e.rethrowFromSystemServer();
1709             }
1710         }
1711 
1712         /**
1713          * Notifies Interactive APP session when a track is selected.
1714          */
notifyTrackSelected(int type, String trackId)1715         public void notifyTrackSelected(int type, String trackId) {
1716             if (mToken == null) {
1717                 Log.w(TAG, "The session has been already released");
1718                 return;
1719             }
1720             try {
1721                 mService.notifyTrackSelected(mToken, type, trackId, mUserId);
1722             } catch (RemoteException e) {
1723                 throw e.rethrowFromSystemServer();
1724             }
1725         }
1726 
1727         /**
1728          * Notifies Interactive APP session when tracks are changed.
1729          */
notifyTracksChanged(List<TvTrackInfo> tracks)1730         public void notifyTracksChanged(List<TvTrackInfo> tracks) {
1731             if (mToken == null) {
1732                 Log.w(TAG, "The session has been already released");
1733                 return;
1734             }
1735             try {
1736                 mService.notifyTracksChanged(mToken, tracks, mUserId);
1737             } catch (RemoteException e) {
1738                 throw e.rethrowFromSystemServer();
1739             }
1740         }
1741 
1742         /**
1743          * Notifies Interactive APP session when video is available.
1744          */
notifyVideoAvailable()1745         public void notifyVideoAvailable() {
1746             if (mToken == null) {
1747                 Log.w(TAG, "The session has been already released");
1748                 return;
1749             }
1750             try {
1751                 mService.notifyVideoAvailable(mToken, mUserId);
1752             } catch (RemoteException e) {
1753                 throw e.rethrowFromSystemServer();
1754             }
1755         }
1756 
1757         /**
1758          * Notifies Interactive APP session when video is unavailable.
1759          */
notifyVideoUnavailable(int reason)1760         public void notifyVideoUnavailable(int reason) {
1761             if (mToken == null) {
1762                 Log.w(TAG, "The session has been already released");
1763                 return;
1764             }
1765             try {
1766                 mService.notifyVideoUnavailable(mToken, reason, mUserId);
1767             } catch (RemoteException e) {
1768                 throw e.rethrowFromSystemServer();
1769             }
1770         }
1771 
1772         /**
1773          * Notifies Interactive app session when the video freeze state is updated
1774          * @param isFrozen Whether or not the video is frozen
1775          */
notifyVideoFreezeUpdated(boolean isFrozen)1776         public void notifyVideoFreezeUpdated(boolean isFrozen) {
1777             if (mToken == null) {
1778                 Log.w(TAG, "The session has been already released");
1779                 return;
1780             }
1781             try {
1782                 mService.notifyVideoFreezeUpdated(mToken, isFrozen, mUserId);
1783             } catch (RemoteException e) {
1784                 throw e.rethrowFromSystemServer();
1785             }
1786         }
1787 
1788         /**
1789          * Notifies Interactive APP session when content is allowed.
1790          */
notifyContentAllowed()1791         public void notifyContentAllowed() {
1792             if (mToken == null) {
1793                 Log.w(TAG, "The session has been already released");
1794                 return;
1795             }
1796             try {
1797                 mService.notifyContentAllowed(mToken, mUserId);
1798             } catch (RemoteException e) {
1799                 throw e.rethrowFromSystemServer();
1800             }
1801         }
1802 
1803         /**
1804          * Notifies Interactive APP session when content is blocked.
1805          */
notifyContentBlocked(TvContentRating rating)1806         public void notifyContentBlocked(TvContentRating rating) {
1807             if (mToken == null) {
1808                 Log.w(TAG, "The session has been already released");
1809                 return;
1810             }
1811             try {
1812                 mService.notifyContentBlocked(mToken, rating.flattenToString(), mUserId);
1813             } catch (RemoteException e) {
1814                 throw e.rethrowFromSystemServer();
1815             }
1816         }
1817 
1818         /**
1819          * Notifies Interactive APP session when signal strength is changed.
1820          */
notifySignalStrength(int strength)1821         public void notifySignalStrength(int strength) {
1822             if (mToken == null) {
1823                 Log.w(TAG, "The session has been already released");
1824                 return;
1825             }
1826             try {
1827                 mService.notifySignalStrength(mToken, strength, mUserId);
1828             } catch (RemoteException e) {
1829                 throw e.rethrowFromSystemServer();
1830             }
1831         }
1832 
1833         /**
1834          * Notifies Interactive APP session when a new TV message is received.
1835          */
notifyTvMessage(int type, Bundle data)1836         public void notifyTvMessage(int type, Bundle data) {
1837             if (mToken == null) {
1838                 Log.w(TAG, "The session has been already released");
1839                 return;
1840             }
1841             try {
1842                 mService.notifyTvMessage(mToken, type, data, mUserId);
1843             } catch (RemoteException e) {
1844                 throw e.rethrowFromSystemServer();
1845             }
1846         }
1847 
flushPendingEventsLocked()1848         private void flushPendingEventsLocked() {
1849             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
1850 
1851             final int count = mPendingEvents.size();
1852             for (int i = 0; i < count; i++) {
1853                 int seq = mPendingEvents.keyAt(i);
1854                 Message msg = mHandler.obtainMessage(
1855                         InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
1856                 msg.setAsynchronous(true);
1857                 msg.sendToTarget();
1858             }
1859         }
1860 
releaseInternal()1861         private void releaseInternal() {
1862             mToken = null;
1863             synchronized (mHandler) {
1864                 if (mInputChannel != null) {
1865                     if (mSender != null) {
1866                         flushPendingEventsLocked();
1867                         mSender.dispose();
1868                         mSender = null;
1869                     }
1870                     mInputChannel.dispose();
1871                     mInputChannel = null;
1872                 }
1873             }
1874             synchronized (mSessionCallbackRecordMap) {
1875                 mSessionCallbackRecordMap.delete(mSeq);
1876             }
1877         }
1878 
obtainPendingEventLocked(InputEvent event, Object token, FinishedInputEventCallback callback, Handler handler)1879         private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
1880                 FinishedInputEventCallback callback, Handler handler) {
1881             PendingEvent p = mPendingEventPool.acquire();
1882             if (p == null) {
1883                 p = new PendingEvent();
1884             }
1885             p.mEvent = event;
1886             p.mEventToken = token;
1887             p.mCallback = callback;
1888             p.mEventHandler = handler;
1889             return p;
1890         }
1891 
1892         // Assumes the event has already been removed from the queue.
invokeFinishedInputEventCallback(PendingEvent p, boolean handled)1893         void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
1894             p.mHandled = handled;
1895             if (p.mEventHandler.getLooper().isCurrentThread()) {
1896                 // Already running on the callback handler thread so we can send the callback
1897                 // immediately.
1898                 p.run();
1899             } else {
1900                 // Post the event to the callback handler thread.
1901                 // In this case, the callback will be responsible for recycling the event.
1902                 Message msg = Message.obtain(p.mEventHandler, p);
1903                 msg.setAsynchronous(true);
1904                 msg.sendToTarget();
1905             }
1906         }
1907 
1908         // Must be called on the main looper
sendInputEventAndReportResultOnMainLooper(PendingEvent p)1909         private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
1910             synchronized (mHandler) {
1911                 int result = sendInputEventOnMainLooperLocked(p);
1912                 if (result == DISPATCH_IN_PROGRESS) {
1913                     return;
1914                 }
1915             }
1916 
1917             invokeFinishedInputEventCallback(p, false);
1918         }
1919 
sendInputEventOnMainLooperLocked(PendingEvent p)1920         private int sendInputEventOnMainLooperLocked(PendingEvent p) {
1921             if (mInputChannel != null) {
1922                 if (mSender == null) {
1923                     mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
1924                 }
1925 
1926                 final InputEvent event = p.mEvent;
1927                 final int seq = event.getSequenceNumber();
1928                 if (mSender.sendInputEvent(seq, event)) {
1929                     mPendingEvents.put(seq, p);
1930                     Message msg = mHandler.obtainMessage(
1931                             InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1932                     msg.setAsynchronous(true);
1933                     mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
1934                     return DISPATCH_IN_PROGRESS;
1935                 }
1936 
1937                 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
1938                         + event);
1939             }
1940             return DISPATCH_NOT_HANDLED;
1941         }
1942 
finishedInputEvent(int seq, boolean handled, boolean timeout)1943         void finishedInputEvent(int seq, boolean handled, boolean timeout) {
1944             final PendingEvent p;
1945             synchronized (mHandler) {
1946                 int index = mPendingEvents.indexOfKey(seq);
1947                 if (index < 0) {
1948                     return; // spurious, event already finished or timed out
1949                 }
1950 
1951                 p = mPendingEvents.valueAt(index);
1952                 mPendingEvents.removeAt(index);
1953 
1954                 if (timeout) {
1955                     Log.w(TAG, "Timeout waiting for session to handle input event after "
1956                             + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
1957                 } else {
1958                     mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
1959                 }
1960             }
1961 
1962             invokeFinishedInputEventCallback(p, handled);
1963         }
1964 
recyclePendingEventLocked(PendingEvent p)1965         private void recyclePendingEventLocked(PendingEvent p) {
1966             p.recycle();
1967             mPendingEventPool.release(p);
1968         }
1969 
1970         /**
1971          * Callback that is invoked when an input event that was dispatched to this session has been
1972          * finished.
1973          *
1974          * @hide
1975          */
1976         public interface FinishedInputEventCallback {
1977             /**
1978              * Called when the dispatched input event is finished.
1979              *
1980              * @param token A token passed to {@link #dispatchInputEvent}.
1981              * @param handled {@code true} if the dispatched input event was handled properly.
1982              *            {@code false} otherwise.
1983              */
onFinishedInputEvent(Object token, boolean handled)1984             void onFinishedInputEvent(Object token, boolean handled);
1985         }
1986 
1987         private final class InputEventHandler extends Handler {
1988             public static final int MSG_SEND_INPUT_EVENT = 1;
1989             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
1990             public static final int MSG_FLUSH_INPUT_EVENT = 3;
1991 
InputEventHandler(Looper looper)1992             InputEventHandler(Looper looper) {
1993                 super(looper, null, true);
1994             }
1995 
1996             @Override
handleMessage(Message msg)1997             public void handleMessage(Message msg) {
1998                 switch (msg.what) {
1999                     case MSG_SEND_INPUT_EVENT: {
2000                         sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
2001                         return;
2002                     }
2003                     case MSG_TIMEOUT_INPUT_EVENT: {
2004                         finishedInputEvent(msg.arg1, false, true);
2005                         return;
2006                     }
2007                     case MSG_FLUSH_INPUT_EVENT: {
2008                         finishedInputEvent(msg.arg1, false, false);
2009                         return;
2010                     }
2011                 }
2012             }
2013         }
2014 
2015         private final class TvInputEventSender extends InputEventSender {
TvInputEventSender(InputChannel inputChannel, Looper looper)2016             TvInputEventSender(InputChannel inputChannel, Looper looper) {
2017                 super(inputChannel, looper);
2018             }
2019 
2020             @Override
onInputEventFinished(int seq, boolean handled)2021             public void onInputEventFinished(int seq, boolean handled) {
2022                 finishedInputEvent(seq, handled, false);
2023             }
2024         }
2025 
2026         private final class PendingEvent implements Runnable {
2027             public InputEvent mEvent;
2028             public Object mEventToken;
2029             public FinishedInputEventCallback mCallback;
2030             public Handler mEventHandler;
2031             public boolean mHandled;
2032 
recycle()2033             public void recycle() {
2034                 mEvent = null;
2035                 mEventToken = null;
2036                 mCallback = null;
2037                 mEventHandler = null;
2038                 mHandled = false;
2039             }
2040 
2041             @Override
run()2042             public void run() {
2043                 mCallback.onFinishedInputEvent(mEventToken, mHandled);
2044 
2045                 synchronized (mEventHandler) {
2046                     recyclePendingEventLocked(this);
2047                 }
2048             }
2049         }
2050     }
2051 
2052     private static final class SessionCallbackRecord {
2053         private final SessionCallback mSessionCallback;
2054         private final Handler mHandler;
2055         private Session mSession;
2056 
SessionCallbackRecord(SessionCallback sessionCallback, Handler handler)2057         SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
2058             mSessionCallback = sessionCallback;
2059             mHandler = handler;
2060         }
2061 
postSessionCreated(final Session session)2062         void postSessionCreated(final Session session) {
2063             mSession = session;
2064             mHandler.post(new Runnable() {
2065                 @Override
2066                 public void run() {
2067                     mSessionCallback.onSessionCreated(session);
2068                 }
2069             });
2070         }
2071 
postSessionReleased()2072         void postSessionReleased() {
2073             mHandler.post(new Runnable() {
2074                 @Override
2075                 public void run() {
2076                     mSessionCallback.onSessionReleased(mSession);
2077                 }
2078             });
2079         }
2080 
postLayoutSurface(final int left, final int top, final int right, final int bottom)2081         void postLayoutSurface(final int left, final int top, final int right,
2082                 final int bottom) {
2083             mHandler.post(new Runnable() {
2084                 @Override
2085                 public void run() {
2086                     mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
2087                 }
2088             });
2089         }
2090 
postBroadcastInfoRequest(final BroadcastInfoRequest request)2091         void postBroadcastInfoRequest(final BroadcastInfoRequest request) {
2092             mHandler.post(new Runnable() {
2093                 @Override
2094                 public void run() {
2095                     if (mSession.getInputSession() != null) {
2096                         mSession.getInputSession().requestBroadcastInfo(request);
2097                     }
2098                 }
2099             });
2100         }
2101 
postRemoveBroadcastInfo(final int requestId)2102         void postRemoveBroadcastInfo(final int requestId) {
2103             mHandler.post(new Runnable() {
2104                 @Override
2105                 public void run() {
2106                     if (mSession.getInputSession() != null) {
2107                         mSession.getInputSession().removeBroadcastInfo(requestId);
2108                     }
2109                 }
2110             });
2111         }
2112 
postCommandRequest( final @TvInteractiveAppService.PlaybackCommandType String cmdType, final Bundle parameters)2113         void postCommandRequest(
2114                 final @TvInteractiveAppService.PlaybackCommandType String cmdType,
2115                 final Bundle parameters) {
2116             mHandler.post(new Runnable() {
2117                 @Override
2118                 public void run() {
2119                     mSessionCallback.onCommandRequest(mSession, cmdType, parameters);
2120                 }
2121             });
2122         }
2123 
postTimeShiftCommandRequest( final @TvInteractiveAppService.TimeShiftCommandType String cmdType, final Bundle parameters)2124         void postTimeShiftCommandRequest(
2125                 final @TvInteractiveAppService.TimeShiftCommandType String cmdType,
2126                 final Bundle parameters) {
2127             mHandler.post(new Runnable() {
2128                 @Override
2129                 public void run() {
2130                     mSessionCallback.onTimeShiftCommandRequest(mSession, cmdType, parameters);
2131                 }
2132             });
2133         }
2134 
postSetVideoBounds(Rect rect)2135         void postSetVideoBounds(Rect rect) {
2136             mHandler.post(new Runnable() {
2137                 @Override
2138                 public void run() {
2139                     mSessionCallback.onSetVideoBounds(mSession, rect);
2140                 }
2141             });
2142         }
2143 
postRequestCurrentVideoBounds()2144         void postRequestCurrentVideoBounds() {
2145             mHandler.post(new Runnable() {
2146                 @Override
2147                 public void run() {
2148                     mSessionCallback.onRequestCurrentVideoBounds(mSession);
2149                 }
2150             });
2151         }
2152 
postRequestCurrentChannelUri()2153         void postRequestCurrentChannelUri() {
2154             mHandler.post(new Runnable() {
2155                 @Override
2156                 public void run() {
2157                     mSessionCallback.onRequestCurrentChannelUri(mSession);
2158                 }
2159             });
2160         }
2161 
postRequestCurrentChannelLcn()2162         void postRequestCurrentChannelLcn() {
2163             mHandler.post(new Runnable() {
2164                 @Override
2165                 public void run() {
2166                     mSessionCallback.onRequestCurrentChannelLcn(mSession);
2167                 }
2168             });
2169         }
2170 
postRequestStreamVolume()2171         void postRequestStreamVolume() {
2172             mHandler.post(new Runnable() {
2173                 @Override
2174                 public void run() {
2175                     mSessionCallback.onRequestStreamVolume(mSession);
2176                 }
2177             });
2178         }
2179 
postRequestTrackInfoList()2180         void postRequestTrackInfoList() {
2181             mHandler.post(new Runnable() {
2182                 @Override
2183                 public void run() {
2184                     mSessionCallback.onRequestTrackInfoList(mSession);
2185                 }
2186             });
2187         }
2188 
postRequestSelectedTrackInfo()2189         void postRequestSelectedTrackInfo() {
2190             mHandler.post(new Runnable() {
2191                 @Override
2192                 public void run() {
2193                     mSessionCallback.onRequestSelectedTrackInfo(mSession);
2194                 }
2195             });
2196         }
2197 
postRequestCurrentTvInputId()2198         void postRequestCurrentTvInputId() {
2199             mHandler.post(new Runnable() {
2200                 @Override
2201                 public void run() {
2202                     mSessionCallback.onRequestCurrentTvInputId(mSession);
2203                 }
2204             });
2205         }
2206 
postRequestTimeShiftMode()2207         void postRequestTimeShiftMode() {
2208             mHandler.post(new Runnable() {
2209                 @Override
2210                 public void run() {
2211                     mSessionCallback.onRequestTimeShiftMode(mSession);
2212                 }
2213             });
2214         }
2215 
postRequestAvailableSpeeds()2216         void postRequestAvailableSpeeds() {
2217             mHandler.post(new Runnable() {
2218                 @Override
2219                 public void run() {
2220                     mSessionCallback.onRequestAvailableSpeeds(mSession);
2221                 }
2222             });
2223         }
2224 
postRequestStartRecording(String requestId, Uri programUri)2225         void postRequestStartRecording(String requestId, Uri programUri) {
2226             mHandler.post(new Runnable() {
2227                 @Override
2228                 public void run() {
2229                     mSessionCallback.onRequestStartRecording(mSession, requestId, programUri);
2230                 }
2231             });
2232         }
2233 
postRequestStopRecording(String recordingId)2234         void postRequestStopRecording(String recordingId) {
2235             mHandler.post(new Runnable() {
2236                 @Override
2237                 public void run() {
2238                     mSessionCallback.onRequestStopRecording(mSession, recordingId);
2239                 }
2240             });
2241         }
2242 
postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, Uri programUri, Bundle params)2243         void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
2244                 Uri programUri, Bundle params) {
2245             mHandler.post(new Runnable() {
2246                 @Override
2247                 public void run() {
2248                     mSessionCallback.onRequestScheduleRecording(
2249                             mSession, requestId, inputId, channelUri, programUri, params);
2250                 }
2251             });
2252         }
2253 
postRequestScheduleRecording(String requestId, String inputId, Uri channelUri, long startTime, long duration, int repeatDays, Bundle params)2254         void postRequestScheduleRecording(String requestId, String inputId, Uri channelUri,
2255                 long startTime, long duration, int repeatDays, Bundle params) {
2256             mHandler.post(new Runnable() {
2257                 @Override
2258                 public void run() {
2259                     mSessionCallback.onRequestScheduleRecording(mSession, requestId, inputId,
2260                             channelUri, startTime, duration, repeatDays, params);
2261                 }
2262             });
2263         }
2264 
postRequestSigning(String id, String algorithm, String alias, byte[] data)2265         void postRequestSigning(String id, String algorithm, String alias, byte[] data) {
2266             mHandler.post(new Runnable() {
2267                 @Override
2268                 public void run() {
2269                     mSessionCallback.onRequestSigning(mSession, id, algorithm, alias, data);
2270                 }
2271             });
2272         }
2273 
postRequestSigning(String id, String algorithm, String host, int port, byte[] data)2274         void postRequestSigning(String id, String algorithm, String host, int port,
2275                 byte[] data) {
2276             mHandler.post(new Runnable() {
2277                 @Override
2278                 public void run() {
2279                     mSessionCallback.onRequestSigning(mSession, id, algorithm, host,
2280                             port, data);
2281                 }
2282             });
2283         }
2284 
postRequestCertificate(String host, int port)2285         void postRequestCertificate(String host, int port) {
2286             mHandler.post(new Runnable() {
2287                 @Override
2288                 public void run() {
2289                     mSessionCallback.onRequestCertificate(mSession, host, port);
2290                 }
2291             });
2292         }
2293 
postRequestTvRecordingInfo(String recordingId)2294         void postRequestTvRecordingInfo(String recordingId) {
2295             mHandler.post(new Runnable() {
2296                 @Override
2297                 public void run() {
2298                     mSessionCallback.onRequestTvRecordingInfo(mSession, recordingId);
2299                 }
2300             });
2301         }
2302 
postRequestTvRecordingInfoList(int type)2303         void postRequestTvRecordingInfoList(int type) {
2304             mHandler.post(new Runnable() {
2305                 @Override
2306                 public void run() {
2307                     mSessionCallback.onRequestTvRecordingInfoList(mSession, type);
2308                 }
2309             });
2310         }
2311 
postSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo)2312         void postSetTvRecordingInfo(String recordingId, TvRecordingInfo recordingInfo) {
2313             mHandler.post(new Runnable() {
2314                 @Override
2315                 public void run() {
2316                     mSessionCallback.onSetTvRecordingInfo(mSession, recordingId, recordingInfo);
2317                 }
2318             });
2319         }
2320 
postAdRequest(final AdRequest request)2321         void postAdRequest(final AdRequest request) {
2322             mHandler.post(new Runnable() {
2323                 @Override
2324                 public void run() {
2325                     if (mSession.getInputSession() != null) {
2326                         mSession.getInputSession().requestAd(request);
2327                     }
2328                 }
2329             });
2330         }
2331 
postSessionStateChanged(int state, int err)2332         void postSessionStateChanged(int state, int err) {
2333             mHandler.post(new Runnable() {
2334                 @Override
2335                 public void run() {
2336                     mSessionCallback.onSessionStateChanged(mSession, state, err);
2337                 }
2338             });
2339         }
2340 
postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId)2341         void postBiInteractiveAppCreated(Uri biIAppUri, String biIAppId) {
2342             mHandler.post(new Runnable() {
2343                 @Override
2344                 public void run() {
2345                     mSessionCallback.onBiInteractiveAppCreated(mSession, biIAppUri, biIAppId);
2346                 }
2347             });
2348         }
2349 
postTeletextAppStateChanged(int state)2350         void postTeletextAppStateChanged(int state) {
2351             mHandler.post(new Runnable() {
2352                 @Override
2353                 public void run() {
2354                     mSessionCallback.onTeletextAppStateChanged(mSession, state);
2355                 }
2356             });
2357         }
2358 
postAdBufferReady(AdBuffer buffer)2359         void postAdBufferReady(AdBuffer buffer) {
2360             mHandler.post(new Runnable() {
2361                 @Override
2362                 public void run() {
2363                     if (mSession.getInputSession() != null) {
2364                         mSession.getInputSession().notifyAdBufferReady(buffer);
2365                     }
2366                 }
2367             });
2368         }
2369     }
2370 
2371     /**
2372      * Interface used to receive the created session.
2373      * @hide
2374      */
2375     public abstract static class SessionCallback {
2376         /**
2377          * This is called after {@link TvInteractiveAppManager#createSession} has been processed.
2378          *
2379          * @param session A {@link TvInteractiveAppManager.Session} instance created. This can be
2380          *                {@code null} if the creation request failed.
2381          */
onSessionCreated(@ullable Session session)2382         public void onSessionCreated(@Nullable Session session) {
2383         }
2384 
2385         /**
2386          * This is called when {@link TvInteractiveAppManager.Session} is released.
2387          * This typically happens when the process hosting the session has crashed or been killed.
2388          *
2389          * @param session the {@link TvInteractiveAppManager.Session} instance released.
2390          */
onSessionReleased(@onNull Session session)2391         public void onSessionReleased(@NonNull Session session) {
2392         }
2393 
2394         /**
2395          * This is called when {@link TvInteractiveAppService.Session#layoutSurface} is called to
2396          * change the layout of surface.
2397          *
2398          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2399          * @param left Left position.
2400          * @param top Top position.
2401          * @param right Right position.
2402          * @param bottom Bottom position.
2403          */
onLayoutSurface(Session session, int left, int top, int right, int bottom)2404         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
2405         }
2406 
2407         /**
2408          * This is called when {@link TvInteractiveAppService.Session#requestCommand} is called.
2409          *
2410          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2411          * @param cmdType type of the command.
2412          * @param parameters parameters of the command.
2413          */
onCommandRequest( Session session, @TvInteractiveAppService.PlaybackCommandType String cmdType, Bundle parameters)2414         public void onCommandRequest(
2415                 Session session,
2416                 @TvInteractiveAppService.PlaybackCommandType String cmdType,
2417                 Bundle parameters) {
2418         }
2419 
2420         /**
2421          * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftCommand} is
2422          * called.
2423          *
2424          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2425          * @param cmdType type of the time shift command.
2426          * @param parameters parameters of the command.
2427          */
onTimeShiftCommandRequest( Session session, @TvInteractiveAppService.TimeShiftCommandType String cmdType, Bundle parameters)2428         public void onTimeShiftCommandRequest(
2429                 Session session,
2430                 @TvInteractiveAppService.TimeShiftCommandType String cmdType,
2431                 Bundle parameters) {
2432         }
2433 
2434         /**
2435          * This is called when {@link TvInteractiveAppService.Session#setVideoBounds} is called.
2436          *
2437          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2438          */
onSetVideoBounds(Session session, Rect rect)2439         public void onSetVideoBounds(Session session, Rect rect) {
2440         }
2441 
2442         /**
2443          * This is called when {@link TvInteractiveAppService.Session#requestCurrentVideoBounds} is
2444          * called.
2445          *
2446          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2447          */
onRequestCurrentVideoBounds(Session session)2448         public void onRequestCurrentVideoBounds(Session session) {
2449         }
2450 
2451         /**
2452          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelUri} is
2453          * called.
2454          *
2455          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2456          */
onRequestCurrentChannelUri(Session session)2457         public void onRequestCurrentChannelUri(Session session) {
2458         }
2459 
2460         /**
2461          * This is called when {@link TvInteractiveAppService.Session#requestCurrentChannelLcn} is
2462          * called.
2463          *
2464          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2465          */
onRequestCurrentChannelLcn(Session session)2466         public void onRequestCurrentChannelLcn(Session session) {
2467         }
2468 
2469         /**
2470          * This is called when {@link TvInteractiveAppService.Session#requestStreamVolume} is
2471          * called.
2472          *
2473          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2474          */
onRequestStreamVolume(Session session)2475         public void onRequestStreamVolume(Session session) {
2476         }
2477 
2478         /**
2479          * This is called when {@link TvInteractiveAppService.Session#requestTrackInfoList} is
2480          * called.
2481          *
2482          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2483          */
onRequestTrackInfoList(Session session)2484         public void onRequestTrackInfoList(Session session) {
2485         }
2486 
2487         /**
2488          * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
2489          * called.
2490          *
2491          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2492          */
onRequestSelectedTrackInfo(Session session)2493         public void onRequestSelectedTrackInfo(Session session) {
2494         }
2495 
2496         /**
2497          * This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
2498          * called.
2499          *
2500          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2501          */
onRequestCurrentTvInputId(Session session)2502         public void onRequestCurrentTvInputId(Session session) {
2503         }
2504 
2505         /**
2506          * This is called when {@link TvInteractiveAppService.Session#requestTimeShiftMode()} is
2507          * called.
2508          *
2509          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2510          */
onRequestTimeShiftMode(Session session)2511         public void onRequestTimeShiftMode(Session session) {
2512         }
2513 
2514         /**
2515          * This is called when {@link TvInteractiveAppService.Session#requestAvailableSpeeds()} is
2516          * called.
2517          *
2518          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2519          */
onRequestAvailableSpeeds(Session session)2520         public void onRequestAvailableSpeeds(Session session) {
2521         }
2522 
2523         /**
2524          * This is called when {@link TvInteractiveAppService.Session#requestStartRecording} is
2525          * called.
2526          *
2527          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2528          * @param programUri The Uri of the program to be recorded.
2529          */
onRequestStartRecording(Session session, String requestId, Uri programUri)2530         public void onRequestStartRecording(Session session, String requestId, Uri programUri) {
2531         }
2532 
2533         /**
2534          * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)}
2535          * is called.
2536          *
2537          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2538          * @param recordingId The recordingId of the recording to be stopped.
2539          */
onRequestStopRecording(Session session, String recordingId)2540         public void onRequestStopRecording(Session session, String recordingId) {
2541         }
2542 
2543         /**
2544          * This is called when
2545          * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, Uri, Bundle)}
2546          * is called.
2547          *
2548          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2549          * @param inputId The ID of the TV input for the given channel.
2550          * @param channelUri The URI of a channel to be recorded.
2551          * @param programUri The URI of the TV program to be recorded.
2552          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
2553          *            name, i.e. prefixed with a package name you own, so that different developers
2554          *            will not create conflicting keys.
2555          * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
2556          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
2557          */
onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri, @NonNull Bundle params)2558         public void onRequestScheduleRecording(Session session, @NonNull String requestId,
2559                 @NonNull String inputId, @NonNull Uri channelUri, @NonNull Uri programUri,
2560                 @NonNull Bundle params) {
2561         }
2562 
2563         /**
2564          * This is called when
2565          * {@link TvInteractiveAppService.Session#requestScheduleRecording(String, String, Uri, long, long, int, Bundle)}
2566          * is called.
2567          *
2568          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2569          * @param inputId The ID of the TV input for the given channel.
2570          * @param channelUri The URI of a channel to be recorded.
2571          * @param startTime The start time of the recording in milliseconds since epoch.
2572          * @param duration The duration of the recording in milliseconds.
2573          * @param repeatDays The repeated days. 0 if not repeated.
2574          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
2575          *            name, i.e. prefixed with a package name you own, so that different developers
2576          *            will not create conflicting keys.
2577          * @see android.media.tv.TvRecordingClient#tune(String, Uri, Bundle)
2578          * @see android.media.tv.TvRecordingClient#startRecording(Uri)
2579          */
onRequestScheduleRecording(Session session, @NonNull String requestId, @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration, int repeatDays, @NonNull Bundle params)2580         public void onRequestScheduleRecording(Session session, @NonNull String requestId,
2581                 @NonNull String inputId, @NonNull Uri channelUri, long startTime, long duration,
2582                 int repeatDays, @NonNull Bundle params) {
2583         }
2584 
2585         /**
2586          * This is called when
2587          * {@link TvInteractiveAppService.Session#setTvRecordingInfo(String, TvRecordingInfo)} is
2588          * called.
2589          *
2590          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2591          * @param recordingId The recordingId of the recording which will have the info set.
2592          * @param recordingInfo The recording info to set to the recording.
2593          */
onSetTvRecordingInfo(Session session, String recordingId, TvRecordingInfo recordingInfo)2594         public void onSetTvRecordingInfo(Session session, String recordingId,
2595                 TvRecordingInfo recordingInfo) {
2596         }
2597 
2598         /**
2599          * This is called when {@link TvInteractiveAppService.Session#requestTvRecordingInfo} is
2600          * called.
2601          *
2602          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2603          * @param recordingId The recordingId of the recording to be stopped.
2604          */
onRequestTvRecordingInfo(Session session, String recordingId)2605         public void onRequestTvRecordingInfo(Session session, String recordingId) {
2606         }
2607 
2608         /**
2609          * This is called when {@link TvInteractiveAppService.Session#requestTvRecordingInfoList} is
2610          * called.
2611          *
2612          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2613          * @param type The type of recordings to return
2614          */
onRequestTvRecordingInfoList(Session session, @TvRecordingInfo.TvRecordingListType int type)2615         public void onRequestTvRecordingInfoList(Session session,
2616                 @TvRecordingInfo.TvRecordingListType int type) {
2617         }
2618 
2619         /**
2620          * This is called when
2621          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, byte[])} is
2622          * called.
2623          *
2624          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2625          * @param signingId the ID to identify the request.
2626          * @param algorithm the standard name of the signature algorithm requested, such as
2627          *                  MD5withRSA, SHA256withDSA, etc.
2628          * @param alias the alias of the corresponding {@link java.security.KeyStore}.
2629          * @param data the original bytes to be signed.
2630          */
onRequestSigning( Session session, String signingId, String algorithm, String alias, byte[] data)2631         public void onRequestSigning(
2632                 Session session, String signingId, String algorithm, String alias, byte[] data) {
2633         }
2634 
2635         /**
2636          * This is called when
2637          * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
2638          * is called.
2639          *
2640          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2641          * @param signingId the ID to identify the request.
2642          * @param algorithm the standard name of the signature algorithm requested, such as
2643          *                  MD5withRSA, SHA256withDSA, etc.
2644          * @param host The host of the SSL CLient Authentication Server
2645          * @param port The port of the SSL Client Authentication Server
2646          * @param data the original bytes to be signed.
2647          */
onRequestSigning( Session session, String signingId, String algorithm, String host, int port, byte[] data)2648         public void onRequestSigning(
2649                 Session session, String signingId, String algorithm, String host,
2650                 int port, byte[] data) {
2651         }
2652 
2653         /**
2654          * This is called when the service requests a SSL certificate for client validation.
2655          *
2656          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
2657          * @param host the host name of the SSL authentication server.
2658          * @param port the port of the SSL authentication server. E.g., 443
2659          */
onRequestCertificate(Session session, String host, int port)2660         public void onRequestCertificate(Session session, String host, int port) {
2661         }
2662 
2663         /**
2664          * This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
2665          * called.
2666          *
2667          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2668          * @param state the current state.
2669          */
onSessionStateChanged( Session session, @InteractiveAppState int state, @ErrorCode int err)2670         public void onSessionStateChanged(
2671                 Session session,
2672                 @InteractiveAppState int state,
2673                 @ErrorCode int err) {
2674         }
2675 
2676         /**
2677          * This is called when {@link TvInteractiveAppService.Session#notifyBiInteractiveAppCreated}
2678          * is called.
2679          *
2680          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2681          * @param biIAppUri URI associated this BI interactive app. This is the same URI in
2682          *                  {@link Session#createBiInteractiveApp(Uri, Bundle)}
2683          * @param biIAppId BI interactive app ID, which can be used to destroy the BI interactive
2684          *                 app.
2685          */
onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId)2686         public void onBiInteractiveAppCreated(Session session, Uri biIAppUri, String biIAppId) {
2687         }
2688 
2689         /**
2690          * This is called when {@link TvInteractiveAppService.Session#notifyTeletextAppStateChanged}
2691          * is called.
2692          *
2693          * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
2694          * @param state the current state.
2695          */
onTeletextAppStateChanged( Session session, @TvInteractiveAppManager.TeletextAppState int state)2696         public void onTeletextAppStateChanged(
2697                 Session session, @TvInteractiveAppManager.TeletextAppState int state) {
2698         }
2699     }
2700 }
2701